From 6ea6b3fee27d51607ca4a5ace46dbc38a4481bcb Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:36:25 -0600 Subject: feat(ui): add support for OSC 8 hyperlinks (#27109) 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. --- runtime/doc/api.txt | 3 +++ runtime/doc/news.txt | 4 ++++ runtime/doc/ui.txt | 4 +++- runtime/lua/vim/_meta/api.lua | 3 +++ runtime/lua/vim/_meta/api_keysets.lua | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index f37d349fd8..90c9d0ccbb 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2783,6 +2783,9 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) drawn by a UI. When set, the UI will receive win_extmark events. Note: the mark is positioned by virt_text attributes. Can be used together with virt_text. + • url: A URL to associate with this extmark. In the TUI, the + OSC 8 control sequence is used to generate a clickable + hyperlink to this URL. Return: ~ Id of the created/updated extmark diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d63d1e6928..a7c4e8147c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -301,6 +301,10 @@ The following new APIs and features were added. • |vim.version.le()| and |vim.version.ge()| are added to |vim.version|. +• |extmarks| can be associated with a URL and URLs are included as a new + highlight attribute. The TUI will display URLs using the OSC 8 control + sequence, enabling clickable text in supporting terminals. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index c81420d1f2..b8d47923ca 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -328,9 +328,11 @@ numerical highlight ids to the actual attributes. `underdotted`: underdotted text. The dots have `special` color. `underdashed`: underdashed text. The dashes have `special` color. `altfont`: alternative font. - `blend`: Blend level (0-100). Could be used by UIs to + `blend`: blend level (0-100). Could be used by UIs to support blending floating windows to the background or to signal a transparent cursor. + `url`: a URL associated with this highlight. UIs can + display this URL however they wish. For absent color keys the default color should be used. Don't store the default value in the table, rather a sentinel value, so that diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f773ddd75c..076aae7dbe 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -607,6 +607,9 @@ function vim.api.nvim_buf_line_count(buffer) end --- drawn by a UI. When set, the UI will receive win_extmark --- events. Note: the mark is positioned by virt_text --- attributes. Can be used together with virt_text. +--- • url: A URL to associate with this extmark. In the TUI, the +--- OSC 8 control sequence is used to generate a clickable +--- hyperlink to this URL. --- @return integer function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 5ebedc977b..00d3ff5bb4 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -192,6 +192,7 @@ error('Cannot require a meta file') --- @field fg_indexed? boolean --- @field bg_indexed? boolean --- @field force? boolean +--- @field url? string --- @class vim.api.keyset.highlight_cterm --- @field bold? boolean @@ -272,6 +273,7 @@ error('Cannot require a meta file') --- @field spell? boolean --- @field ui_watched? boolean --- @field undo_restore? boolean +--- @field url? string --- @class vim.api.keyset.user_command --- @field addr? any -- cgit