-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
feat(ui): add support for OSC 8 hyperlinks #27109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
9d1678c to
4866f46
Compare
|
Demo: Screen.Recording.2024-01-19.at.9.43.09.PM.mov |
4866f46 to
a9a5621
Compare
src/nvim/decoration_defs.h
Outdated
| DecorPriority priority; | ||
| int hl_id; | ||
| schar_T conceal_char; | ||
| String url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As these need allocation anyway, there is no point in having them in DecorHighlightInline (as you note, these be converted to ext anyway so it is just waste of space here). better to use a code path more like line_hl_id etc to construct them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in a90c842
Not sure how best to handle ephemeral extmarks with URLs now. When they are part of DecorHighlightInline they get converted in decor_sh_from_inline. I explicitly am adding a new DecorSignHighlight when sign.url.data is non null for ephemeral extmarks, which works, but maybe there is a better way.
src/nvim/highlight.c
Outdated
| /// @param url The URL to associate with the highlight attribute. An empty string indicates the | ||
| /// end of a URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be how it works in the TUI but in the highlight layer it is better to just say an empty string indicates the lack of an URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is out of date now and reflects a previous implementation, thanks for catching it. Will update the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/nvim/highlight.c
Outdated
| hlattrs.cterm_ae_attr = mask; | ||
| } | ||
|
|
||
| if (HAS_KEY_X(dict, url)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this actually used anywhere? in the API layer the PR guards against this, but this breaks the memoization allocation pattern used above and for future usages it is better to leave a missing implementation to fill in, than a broken one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used when called from ui_client_dict2hlattrs. Commenting this out makes the new test in tui_spec fail.
We could move these lines from dict2hlattrs into ui_client_dict2hlattrs so that it only affects the TUI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@gpanders will this new feature be able to parse and process RFC 3986 URIs? Specifically, what I mean is that a URI may point to a website URL or to some other resource like a file on-disk, or something else. Will users be able to customize the "do it" functionality of these URIs? Currently I'm able to use |
No, all this does is write a control sequence to your terminal emulator. See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda. The terminal controls what happens when you click on the link, not Neovim. |
src/nvim/tui/tui.c
Outdated
|
|
||
| if (attrs.url.data != NULL) { | ||
| StringBuilder sb = KV_INITIAL_VALUE; | ||
| kv_printf(sb, "\x1b]8;;%s\x1b\\", attrs.url.data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should include an id here. attr_id maybe?
| kv_printf(sb, "\x1b]8;;%s\x1b\\", attrs.url.data); | |
| kv_printf(sb, "\x1b]8;id=%d;%s\x1b\\", attr_id, attrs.url.data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tricky. attr_id will make the terminal highlight all URLs with the same attr_id, which we probably don't want. We don't really have a way to uniquely identify each individual hyperlink right now. We could use something like grid position where the URL starts, but don't have a way to easily get that information at the moment.
For now I'll omit the id, and we can solve this if/when it becomes a problem.
src/nvim/tui/tui.c
Outdated
| if (tui->url.data != NULL) { | ||
| out(tui, S_LEN("\x1b]8;;\x1b\\")); | ||
| } | ||
|
|
||
| if (attrs.url.data != NULL) { | ||
| StringBuilder sb = KV_INITIAL_VALUE; | ||
| kv_printf(sb, "\x1b]8;;%s\x1b\\", attrs.url.data); | ||
| out(tui, sb.items, sb.size); | ||
| kv_destroy(sb); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are starting a new URL the old URL (if it exists) does not need to be terminated. It works just like any other SGR sequence (e.g. you don't need to "terminate" one color before starting another).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
182c16d to
65aa325
Compare
65aa325 to
ae159cd
Compare
|
The treesitter changes are non-trivial and will require further discussion so I'm moving those out of this PR. |
c34fb9d to
5e0e935
Compare
5e0e935 to
6b77e8f
Compare
d87d68d to
5323370
Compare
|
This should documented in the docs of |
|
Extmarks can contain URLs which can then be drawn in any supporting UI. In the TUI, for example, URLs are "drawn" by emitting the OSC 8 control sequence to the TTY. On terminals which support the OSC 8 sequence this will create clickable hyperlinks. URLs are treated as inline highlights in the decoration subsystem, so are included in the `DecorSignHighlight` structure. However, unlike other inline highlights they use allocated memory which must be freed, so they set the `ext` flag in `DecorInline` so that their lifetimes are managed along with other allocated memory like virtual text. The decoration subsystem then adds the URLs as a new highlight attribute. The highlight subsystem maintains a set of unique URLs to avoid duplicating allocations for the same string. To attach a URL to an existing highlight attribute we call `hl_add_url` which finds the URL in the set (allocating and adding it if it does not exist) and sets the `url` highlight attribute to the index of the URL in the set (using an index helps keep the size of the `HlAttrs` struct small). This has the potential to lead to an increase in highlight attributes if a URL is used over a range that contains many different highlight attributes, because now each existing attribute must be combined with the URL. In practice, however, URLs typically span a range containing a single highlight (e.g. link text in Markdown), so this is likely just a pathological edge case. When a new highlight attribute is defined with a URL it is copied to all attached UIs with the `hl_attr_define` UI event. The TUI manages its own set of URLs (just like the highlight subsystem) to minimize allocations. The TUI keeps track of which URL is "active" for the cell it is printing. If no URL is active and a cell containing a URL is printed, the opening OSC 8 sequence is emitted and that URL becomes the actively tracked URL. If the cursor is moved while in the middle of a URL span, we emit the terminating OSC sequence to prevent the hyperlink from spanning multiple lines. This does not support nested hyperlinks, but that is a rare (and, frankly, bizarre) use case. If a valid use case for nested hyperlinks ever presents itself we can address that issue then.
Reduces size to 32 bytes per HlAttr
d80ac48 to
cc7849d
Compare
| void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh) | ||
| { | ||
| if (sh.hl_id || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) { | ||
| if (sh.hl_id || (sh.url != NULL) || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to add a kSHIsURL flag instead of checking sh.url? (Sorry for the late review was waiting if bfredl had something to say about it TBH.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it but decided against it because it's duplicating information, so no need to waste a flag. "Non null URL" is the flag, in a sense.
I am not opposed to it though if there are other reasons to use a flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than these checks being more readable if it just used flags I don't think so. But it's so only checked a few times and sh.hl_id is checked directly as well so probably doesn't matter much :)
Extmarks can contain URLs which can then be drawn in any supporting UI. In the TUI, for example, URLs are "drawn" by emitting the OSC 8 control sequence to the TTY. On terminals which support the OSC 8 sequence this will create clickable hyperlinks. URLs are treated as inline highlights in the decoration subsystem, so are included in the `DecorSignHighlight` structure. However, unlike other inline highlights they use allocated memory which must be freed, so they set the `ext` flag in `DecorInline` so that their lifetimes are managed along with other allocated memory like virtual text. The decoration subsystem then adds the URLs as a new highlight attribute. The highlight subsystem maintains a set of unique URLs to avoid duplicating allocations for the same string. To attach a URL to an existing highlight attribute we call `hl_add_url` which finds the URL in the set (allocating and adding it if it does not exist) and sets the `url` highlight attribute to the index of the URL in the set (using an index helps keep the size of the `HlAttrs` struct small). This has the potential to lead to an increase in highlight attributes if a URL is used over a range that contains many different highlight attributes, because now each existing attribute must be combined with the URL. In practice, however, URLs typically span a range containing a single highlight (e.g. link text in Markdown), so this is likely just a pathological edge case. When a new highlight attribute is defined with a URL it is copied to all attached UIs with the `hl_attr_define` UI event. The TUI manages its own set of URLs (just like the highlight subsystem) to minimize allocations. The TUI keeps track of which URL is "active" for the cell it is printing. If no URL is active and a cell containing a URL is printed, the opening OSC 8 sequence is emitted and that URL becomes the actively tracked URL. If the cursor is moved while in the middle of a URL span, we emit the terminating OSC sequence to prevent the hyperlink from spanning multiple lines. This does not support nested hyperlinks, but that is a rare (and, frankly, bizarre) use case. If a valid use case for nested hyperlinks ever presents itself we can address that issue then.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
|
No, but you actually need to set the |
Fixes: #11871
Extmarks can contain URLs which can then be drawn in any supporting UI. In the TUI, for example, URLs are "drawn" by emitting the OSC 8 control sequence to the TTY. On terminals which support the OSC 8 sequence this will create clickable hyperlinks.
URLs are treated as inline highlights in the decoration subsystem, so they are included in the
DecorHighlightInlineandDecorSignHighlightstructures. However, unlike other inline highlights they use allocated memory which must be freed, so they set theextflag inDecorInlineso that their lifetimes are managed along with other allocated memory like virtual text.The decoration subsystem then adds the URLs as a new highlight attribute. The highlight subsystem maintains a set of unique URLs to avoid duplicating allocations for the same string. To attach a URL to an existing highlight attribute we call
hl_add_urlwhich finds the URL in the set (allocating and adding it if it does not exist) and sets theurlhighlight attribute.This has the potential to lead to an increase in highlight attributes if a URL is used over a range that contains many different highlight attributes, because now each existing attribute must be combined with the URL. In practice, however, URLs typically span a range containing a single highlight (e.g. link text in Markdown), so this is likely just a pathological edge case.
When a new highlight attribute is defined with a URL it is copied to all attached UIs with the
hl_attr_defineUI event. The TUI manages its own set of URLs (just like the highlight subsystem) to minimize allocations. The TUI keeps track of which URL is "active" for the cell it is printing. If no URL is active and a cell containing a URL is printed, the opening OSC 8 sequence is emitted and that URL becomes the actively tracked URL. If the cursor is moved while in the middle of a URL span, we emit the terminating OSC sequence to prevent the hyperlink fromspanning multiple lines.
This does not support nested hyperlinks, but that is a rare (and, frankly, bizarre) use case. If a valid use case for nested hyperlinks ever presents itself we can address that issue then.
TODO:
Fixtreesitter/highlight_spec.luatestnews.txt