-
Notifications
You must be signed in to change notification settings - Fork 259
Description
Problem
Links in markdown output are rendered with underline + color styling but are not clickable via Cmd+Click (macOS) or Ctrl+Click (Linux/Windows). The terminal has no way to know the underlined text represents a URL — it is purely visual.
Desired behavior
Links should be Cmd+Click-able (or Ctrl+Click-able) in terminals that support it, opening the URL in the default browser.
Root cause
The fast markdown renderer applies only CSI SGR sequences (color, underline) to links. It never emits OSC 8 hyperlink escape sequences, which is the standard mechanism terminals use to make text clickable.
There are zero references to OSC 8 (\x1b]8;;) anywhere in the codebase.
Current link rendering (fast_renderer.go, lines 1718–1735)
For [text](url) where text ≠ url, we emit:
<ansiLinkText.prefix> linkText <ansiLinkText.suffix> ← bold + color only
<space>
<ansiLink.prefix> (url) <ansiLink.suffix> ← underline + color only
For [url](url) (text = url):
<ansiLink.prefix> linkText <ansiLink.suffix> ← underline + color only
Neither case wraps the visible text with OSC 8 sequences.
What OSC 8 requires
To make text clickable, we need to wrap the visible portion with:
\x1b]8;;<URL>\x1b\\ <visible styled text> \x1b]8;;\x1b\\
The \x1b]8;;<URL>\x1b\\ opens the hyperlink, the visible text is displayed (with whatever CSI styling we want), and \x1b]8;;\x1b\\ closes it.
Supported by: iTerm2, Terminal.app (macOS Sequoia+), GNOME Terminal, Windows Terminal, WezTerm, Alacritty, and others. Unsupported terminals simply ignore the sequences.
Technical analysis
1. Add a helper to emit OSC 8 sequences
A small helper function that wraps styled text in OSC 8:
const (
osc8Open = "\x1b]8;;"
osc8Close = "\x1b\\"
)
func writeOSC8Link(out *strings.Builder, url, visibleText string, style ansiStyle) {
out.WriteString(osc8Open)
out.WriteString(url)
out.WriteString(osc8Close)
style.renderTo(out, visibleText)
out.WriteString(osc8Open)
out.WriteString(osc8Close)
}2. Update renderInlineWithStyleTo link rendering (line ~1718)
Replace the direct ansiLinkText.renderTo / ansiLink.renderTo calls with writeOSC8Link, wrapping both the link text and the URL portion.
3. Update ANSI-aware utility functions
Several functions only handle CSI sequences (\x1b[...m) and would miscount OSC 8 sequences as visible characters:
| Function | Line | What to change |
|---|---|---|
ansiStringWidth() |
2077 | Skip OSC 8 sequences (\x1b]8;...ST) — they have zero visual width |
splitWordsWithStyles() |
2395 | Track/skip OSC 8 sequences when splitting words, same as CSI |
breakWord() |
2484 | Skip OSC 8 sequences when breaking long words at width boundaries |
updateActiveStyles() |
2469 | OSC 8 open/close should not be tracked as "active styles" across line breaks (hyperlinks should not span wrapped lines) |
The common pattern: when encountering \x1b], scan forward to the String Terminator (\x1b\\ or \x07) and skip the entire sequence for width calculations.
4. Update test stripANSI regex
The test helper regex (\x1b\[[0-9;]*m) only strips CSI sequences. It needs to also strip OSC 8 sequences to avoid test breakage:
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m|\x1b]8;[^\x1b]*\x1b\\\\`)5. Existing link test
TestFastRendererLinks (line ~341) already asserts on visible text content. It should be extended to verify the presence of OSC 8 sequences in the raw output.