Module: Rubino::Util::Hyperlink

Defined in:
lib/rubino/util/hyperlink.rb

Overview

OSC 8 terminal hyperlinks — wraps text in escape sequences that a supporting terminal renders as clickable links.

Sequence shape: ‘e]8;;URIe\LABELe]8;;e\` The first `e]8;;` opens the link, `e\` (String Terminator) ends the URI segment, LABEL is what the user sees, and the trailing `e]8;;e\` closes the link.

## Support detection OSC 8 is supported by iTerm2, WezTerm, vscode integrated terminal, Hyper, Ghostty, and kitty. Apple_Terminal does NOT support it and would render the escape codes as visible garbage. Detection is CONSERVATIVE: unknown terminals default to off, so users on Terminal.app or a tmux session whose outer terminal we can’t introspect never see junk in their scrollback.

Override with ‘RUBINO_HYPERLINKS=1` to force on (useful in tmux when you know the outer terminal supports OSC 8) or `=0` to force off. `NO_COLOR=1` also forces off, matching the broader convention used by every other ANSI-emitting tool in this CLI.

## Scope OSC 8 lives ENTIRELY in the CLI adapter. The API adapter emits raw structured events (tool name, arguments hash) and the web UI builds its own ‘<a>` elements from that — terminal escape codes have no business inside a JSON payload.

Constant Summary collapse

OPEN_PREFIX =
"\e]8;;"
CLOSE_SUFFIX =
"\e]8;;\e\\"
ST =

String Terminator

"\e\\"
KNOWN_TERM_PROGRAMS =

Terminals known to render OSC 8 correctly. Conservative list —additions welcome as we confirm support elsewhere.

%w[iTerm.app WezTerm vscode Hyper ghostty].freeze

Class Method Summary collapse

Class Method Details

.file_uri(path) ⇒ Object

Builds a ‘file://` URI for the given path, expanding to absolute so the terminal’s URI handler doesn’t try to resolve it against its own cwd. Returns nil when the path is empty or doesn’t exist — callers should fall back to the raw label in that case.



70
71
72
73
74
75
76
77
# File 'lib/rubino/util/hyperlink.rb', line 70

def self.file_uri(path)
  return nil if path.nil? || path.to_s.empty?

  abs = File.expand_path(path.to_s)
  return nil unless File.exist?(abs)

  "file://#{abs}"
end

.reset!Object

Test-only hook to reset the memoized support flag (specs flip env vars between examples). Not part of the public contract.



50
51
52
# File 'lib/rubino/util/hyperlink.rb', line 50

def self.reset!
  remove_instance_variable(:@supported) if defined?(@supported)
end

.supported?Boolean

True when the current terminal renders OSC 8 hyperlinks. Result is cached per process because env vars don’t change mid-run.

Returns:

  • (Boolean)


42
43
44
45
46
# File 'lib/rubino/util/hyperlink.rb', line 42

def self.supported?
  return @supported if defined?(@supported)

  @supported = compute_support
end

.wrap(label, uri:) ⇒ Object

Wraps LABEL in the OSC 8 sequence pointing to URI. Returns LABEL unchanged when hyperlinks aren’t supported, so callers can use the result unconditionally — no escape codes leak into a Terminal.app scrollback or an SSE payload.



58
59
60
61
62
63
64
# File 'lib/rubino/util/hyperlink.rb', line 58

def self.wrap(label, uri:)
  return label.to_s if label.nil?
  return label.to_s unless supported?
  return label.to_s if uri.nil? || uri.to_s.empty?

  "#{OPEN_PREFIX}#{uri}#{ST}#{label}#{CLOSE_SUFFIX}"
end

.wrap_path(path, label: nil) ⇒ Object

Convenience for the common case: “I have a file path, wrap it as a clickable link to that file.” Pass a different ‘label:` when the displayed text differs from the path (e.g. truncated to fit a header rule).



83
84
85
86
87
88
89
# File 'lib/rubino/util/hyperlink.rb', line 83

def self.wrap_path(path, label: nil)
  uri  = file_uri(path)
  text = (label || path).to_s
  return text if uri.nil?

  wrap(text, uri: uri)
end