Skip to content

feat(tui): make markdown links and URLs clickable in the terminal#2498

Open
silvin-lubecki wants to merge 1 commit intodocker:mainfrom
silvin-lubecki:feat/clickable-hyperlinks
Open

feat(tui): make markdown links and URLs clickable in the terminal#2498
silvin-lubecki wants to merge 1 commit intodocker:mainfrom
silvin-lubecki:feat/clickable-hyperlinks

Conversation

@silvin-lubecki
Copy link
Copy Markdown
Contributor

Problem:
Long URLs (e.g., Grafana dashboard links with encoded query parameters) were rendered as plain text in the TUI and were not clickable. Markdown links like text displayed both the link text AND the full URL, which cluttered the output. URLs inside code blocks that wrapped across multiple lines were impossible to click in any terminal.

Solution:
Emit OSC 8 hyperlink escape sequences around URLs so that terminals with native support (iTerm2, Kitty, WezTerm) render them as clickable links. For terminals without OSC 8 support (like Warp), extend the TUI's existing hover/click URL detection system to read OSC 8 sequences and resolve clicks on the visible link text to the hidden URL.

Changes:

Markdown renderer (fast_renderer.go):

  • text now emits OSC 8 sequences around the visible text instead of displaying the raw URL in parentheses
  • Bare URLs (https://...) in inline text are auto-detected and wrapped in OSC 8 sequences
  • URLs in fenced code blocks are detected and wrapped in OSC 8
  • ansiStringWidth(), splitWordsWithStyles(), breakWord() updated to skip OSC sequences in width calculations and word splitting
  • updateActiveStyles() filters out OSC 8 sequences so hyperlinks are not incorrectly propagated across line wraps
  • fixHyperlinkWrapping() uses lipgloss.WrapWriter to ensure each wrapped line gets its own OSC 8 open/close pair

URL detection (urldetect.go):

  • New extractOSC8Links() parses OSC 8 sequences from rendered lines and maps them to display column positions
  • New findAllURLSpans() merges OSC 8 links with visible URL detection, giving priority to OSC 8 spans on overlap
  • urlAtPosition() and updateHoveredURL() updated to use the combined detection, enabling hover underline and click-to-open for OSC 8 links

Assisted-By: docker-agent

@silvin-lubecki silvin-lubecki requested a review from a team as a code owner April 23, 2026 11:03
@rumpl
Copy link
Copy Markdown
Member

rumpl commented Apr 23, 2026

/review

Copy link
Copy Markdown

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Comment thread pkg/tui/components/markdown/fast_renderer.go
@dgageot
Copy link
Copy Markdown
Member

dgageot commented Apr 24, 2026

@silvin-lubecki did you address the feedback of the agent?

Problem:
Long URLs (e.g., Grafana dashboard links with encoded query parameters)
were rendered as plain text in the TUI and were not clickable. Markdown
links like [text](url) displayed both the link text AND the full URL,
which cluttered the output. URLs inside code blocks that wrapped across
multiple lines were impossible to click in any terminal.

Solution:
Emit OSC 8 hyperlink escape sequences around URLs so that terminals with
native support (iTerm2, Kitty, WezTerm) render them as clickable links.
For terminals without OSC 8 support (like Warp), extend the TUI's
existing hover/click URL detection system to read OSC 8 sequences and
resolve clicks on the visible link text to the hidden URL.

Changes:

Markdown renderer (fast_renderer.go):
- [text](url) now emits OSC 8 sequences around the visible text instead
  of displaying the raw URL in parentheses
- Bare URLs (https://...) in inline text are auto-detected and wrapped
  in OSC 8 sequences
- URLs in fenced code blocks are detected and wrapped in OSC 8
- ansiStringWidth(), splitWordsWithStyles(), breakWord() updated to skip
  OSC sequences in width calculations and word splitting
- updateActiveStyles() filters out OSC 8 sequences so hyperlinks are not
  incorrectly propagated across line wraps
- fixHyperlinkWrapping() uses lipgloss.WrapWriter to ensure each wrapped
  line gets its own OSC 8 open/close pair

URL detection (urldetect.go):
- New extractOSC8Links() parses OSC 8 sequences from rendered lines and
  maps them to display column positions
- New findAllURLSpans() merges OSC 8 links with visible URL detection,
  giving priority to OSC 8 spans on overlap
- urlAtPosition() and updateHoveredURL() updated to use the combined
  detection, enabling hover underline and click-to-open for OSC 8 links

Assisted-By: docker-agent
@silvin-lubecki silvin-lubecki force-pushed the feat/clickable-hyperlinks branch from 881a965 to cadf778 Compare April 27, 2026 10:27
@silvin-lubecki
Copy link
Copy Markdown
Contributor Author

@dgageot PTAL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants