Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change log

## Unreleased

### Added

* Add hyperlink support detection for the Ghostty terminal
by Akshay Birajdar (@the-spectator).

## [v0.2.0] - 2024-11-03

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ The **TTY::Link** supports hyperlink generation in the following terminals:
* `Contour`
* `DomTerm`
* `foot`
* `Ghostty`
* `Hyper`
* `iTerm2`
* `JediTerm`
Expand Down
8 changes: 7 additions & 1 deletion lib/tty/link/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ class Error < StandardError
#
# @api public
class AbstractMethodError < Error
# The error message template
#
# @return [String]
#
# @api private
MESSAGE = "the %<class_name>s class must implement " \
"the `%<method_name>s` method"
private_constant :MESSAGE

# Create an {TTY::Link::AbstractMethodError} instance
#
Expand All @@ -29,7 +35,7 @@ class AbstractMethodError < Error
def initialize(class_name, method_name)
super(format(MESSAGE, class_name: class_name, method_name: method_name))
end
end
end # AbstractMethodError

# Raised when a parameter value doesn't match the allowed values
#
Expand Down
54 changes: 54 additions & 0 deletions lib/tty/link/terminals/ghostty.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require_relative "abstract"

module TTY
class Link
module Terminals
# Responsible for detecting hyperlink support in the Ghostty terminal
#
# @api private
class Ghostty < Abstract
# The Ghostty terminal name pattern
#
# @return [Regexp]
#
# @api private
GHOSTTY = /ghostty/i.freeze
private_constant :GHOSTTY

private

# Detect Ghostty terminal
#
# @example
# ghostty.name?
# # => true
#
# @return [Boolean]
#
# @api private
def name?
!(term_program =~ GHOSTTY).nil?
end

# Detect whether the Ghostty version supports terminal hyperlinks
#
# @example
# ghostty.version?
# # => true
#
# @return [Boolean]
#
# @api private
def version?
return false unless term_program_version

current_semantic_version = semantic_version(term_program_version)

current_semantic_version >= semantic_version(1, 0, 0)
end
end # Ghostty
end # Terminals
end # Link
end # TTY
12 changes: 12 additions & 0 deletions spec/unit/link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@
end
end

context "when Ghostty" do
it "supports links above the 1.0.0 version" do
env = {
"TERM_PROGRAM" => "ghostty",
"TERM_PROGRAM_VERSION" => "1.0.0"
}
link = described_class.new(env: env, output: output)

expect(link.link?).to eq(true)
end
end

context "when Hyper" do
it "supports links above the 2.0.0 version" do
env = {
Expand Down
70 changes: 70 additions & 0 deletions spec/unit/terminals/ghostty_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

RSpec.describe TTY::Link::Terminals::Ghostty, "#link?" do
let(:env_with_name) { {"TERM_PROGRAM" => "ghostty"} }
let(:semantic_version) { TTY::Link::SemanticVersion }

it "doesn't support links without the term program environment variable" do
ghostty = described_class.new(semantic_version, {})

expect(ghostty.link?).to eq(false)
end

it "doesn't support links without a terminal program name" do
env = {"TERM_PROGRAM" => nil}
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(false)
end

it "doesn't support links with a non-Ghostty program name" do
env = {"TERM_PROGRAM" => "other-terminal"}
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(false)
end

it "supports links above the 2.0.0 version with a mixed-case program name" do
env = {
"TERM_PROGRAM" => "GhosTTY",
"TERM_PROGRAM_VERSION" => "2.3.4"
}
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(true)
end

it "supports links above the 1.0.0 version" do
env = env_with_name.merge({"TERM_PROGRAM_VERSION" => "1.0.1"})
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(true)
end

it "supports links on the 1.0.0 version" do
env = env_with_name.merge({"TERM_PROGRAM_VERSION" => "1.0.0"})
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(true)
end

it "doesn't support links below the 1.0.0 version" do
env = env_with_name.merge({"TERM_PROGRAM_VERSION" => "0.9.2"})
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(false)
end

it "doesn't support links without a version" do
env = env_with_name.merge({"TERM_PROGRAM_VERSION" => nil})
ghostty = described_class.new(semantic_version, env)

expect(ghostty.link?).to eq(false)
end

it "doesn't support links without the term program version env variable" do
ghostty = described_class.new(semantic_version, env_with_name)

expect(ghostty.link?).to eq(false)
end
end