Skip to content

fix(renderer): add OSC 8 hyperlink id parameter and onChunks hook for link detection#736

Open
mugnimaestra wants to merge 2 commits intoanomalyco:mainfrom
mugnimaestra:fix/osc8-hyperlink-id
Open

fix(renderer): add OSC 8 hyperlink id parameter and onChunks hook for link detection#736
mugnimaestra wants to merge 2 commits intoanomalyco:mainfrom
mugnimaestra:fix/osc8-hyperlink-id

Conversation

@mugnimaestra
Copy link

@mugnimaestra mugnimaestra commented Feb 24, 2026

Summary

Fixes hyperlinks not working correctly when they wrap across terminal lines, and adds opt-in link detection for tree-sitter rendered code blocks.

Closes #735

Changes

1. fix(renderer): add id parameter to OSC 8 hyperlink sequences

The renderer emitted OSC 8 without the id parameter, so terminals could not identify wrapped link segments as the same hyperlink. Now emits:

\x1b]8;id={linkId};{url}\x1b\\

Links stay open across row boundaries and close naturally when a different link (or no link) is encountered.

2. feat(code): add onChunks hook and detectLinks helper

The tree-sitter path (CodeRenderable) never set link on TextChunk objects, so OSC 8 was never emitted for code blocks.

Following maintainer feedback, this uses hooks instead of hardcoded scope names:

  • onChunks callback on CodeRenderable — runs after treeSitterToTextChunks, before setStyledText
  • detectLinks() exported helper — scans highlights for URL scopes, sets chunk.link = { url } on matches

Uses content.indexOf() to find chunk positions in original content, handling concealed markdown syntax where visible text length differs from source byte positions.

import { detectLinks } from "@opentui/core"

<code filetype="markdown" onChunks={detectLinks}>
  {content}
</code>

Tests

  • 3334 TS tests pass, 1480 native tests pass
  • Added renderer test for wrapped hyperlinks
  • Added 5 unit tests for detectLinks including concealed text edge case

Known Limitation

Visual hover highlighting covers one row at a time for wrapped links. The OSC 8 spec defines id-based cross-row grouping, but no terminal has been verified to implement it. Cmd/Ctrl+Click works correctly across all rows.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 24, 2026

@opentui/core

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core@2fa7fad

@opentui/react

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/react@2fa7fad

@opentui/solid

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/solid@2fa7fad

@opentui/core-darwin-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@2fa7fad

@opentui/core-darwin-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@2fa7fad

@opentui/core-linux-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@2fa7fad

@opentui/core-linux-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@2fa7fad

@opentui/core-win32-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@2fa7fad

@opentui/core-win32-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@2fa7fad

commit: 2fa7fad

@kommander
Copy link
Collaborator

@mugnimaestra the reason why I didn't do the "link detection" in tree-sitter-styled-text was that markup.link.url is language specific and may be different based on the query that is used, so it's not generic. That should be done via hooks for the CodeRenderable. There were hooks introduced in #568 in the CodeRenderable, maybe these can be used? I am fine with opentui exporting a method that does link detection and chunk editing via such a hook.

@mugnimaestra
Copy link
Author

Thanks for the feedback @kommander! I've refactored the approach:

  1. Reverted the hardcoded markup.link.url / string.special.url detection from treeSitterToTextChunks — you're right that those scope names are language-specific and don't belong in the generic pipeline.

  2. Added onChunks callback to CodeRenderable — this runs after treeSitterToTextChunks produces chunks, allowing consumers to post-process TextChunk[] (e.g., add link properties). It follows the same pattern as the existing onHighlight hook from feat(core): Add highlighting callback for custom inline highlighting #568.

  3. Exported detectLinks() helper from @opentui/core — consumers pass this as onChunks to opt-in to link detection. It detects markup.link.url and string.special.url scopes from the highlight data and enriches the corresponding chunks with link: { url }.

Usage in opencode:

import { detectLinks } from "@opentui/core"

<code filetype="markdown" onChunks={detectLinks} ... />

Tested in iTerm2 — OSC 8 hyperlinks now work correctly, including URLs that wrap across multiple terminal lines (thanks to the id parameter fix in renderer.zig + the link detection via this hook).

@mugnimaestra mugnimaestra changed the title fix(renderer): add id parameter to OSC 8 hyperlink sequences fix(renderer): add OSC 8 hyperlink id parameter and onChunks hook for link detection Feb 25, 2026
mugnimaestra added a commit to mugnimaestra/opencode that referenced this pull request Feb 25, 2026
Add detectLinks from @opentui/core as onChunks callback on the
markdown code renderable. This enables OSC 8 hyperlink sequences
for URLs in tree-sitter rendered markdown, making Cmd/Ctrl+Click
work correctly for wrapped links.

Depends on anomalyco/opentui#736
@kommander
Copy link
Collaborator

Cool, I'll re-check shortly.

@mugnimaestra
Copy link
Author

opencode-issues-7.mov

I couldn't make the UI highlighting works, it seems the issues is quite complex so I refrain to do any further, but at least when cmd+click url that gets separated by newline now it still captures the full URL as expected

Add the id parameter to OSC 8 hyperlink escape sequences so terminals
can identify multi-row links as belonging to the same hyperlink.

The id is derived from the existing linkId in cell attributes:
  \x1b]8;id={linkId};{url}\x1b\\

Links are kept open across row boundaries and closed naturally when
a cell with a different linkId is encountered or at end of frame.
Add onChunks callback to CodeRenderable that runs after treeSitterToTextChunks
produces chunks, before setStyledText. This lets consumers modify chunks
without hardcoding language-specific scope names in the core.

Export a detectLinks() helper that scans tree-sitter highlights for URL
scopes and sets chunk.link on matching chunks. Uses content.indexOf()
to handle concealed text offset mismatches where chunk text length
differs from original content byte positions.

Usage:
  import { detectLinks } from "@opentui/core"
  <code filetype="markdown" onChunks={detectLinks}>{content}</code>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core This relates to the core package enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hyperlinks spanning wrapped lines are not grouped (missing 'id' parameter in OSC 8)

2 participants