aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/extmark.c23
-rw-r--r--src/nvim/api/keysets_defs.h2
-rw-r--r--src/nvim/api/ui.c8
-rw-r--r--src/nvim/api/vim.c6
-rw-r--r--src/nvim/decoration.c27
-rw-r--r--src/nvim/decoration_defs.h4
-rw-r--r--src/nvim/highlight.c65
-rw-r--r--src/nvim/highlight_defs.h8
-rw-r--r--src/nvim/highlight_group.c4
-rw-r--r--src/nvim/map_defs.h3
-rw-r--r--src/nvim/terminal.c5
-rw-r--r--src/nvim/tui/tui.c74
-rw-r--r--src/nvim/ui_client.c9
13 files changed, 209 insertions, 29 deletions
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 1f0e867162..27a4b7854f 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -481,6 +481,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// 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.
///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
@@ -494,6 +496,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT;
DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT;
+ char *url = NULL;
bool has_hl = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -678,6 +681,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
has_hl = true;
}
+ if (HAS_KEY(opts, set_extmark, url)) {
+ url = string_to_cstr(opts->url);
+ }
+
if (opts->ui_watched) {
hl.flags |= kSHUIWatched;
if (virt_text.pos == kVPosOverlay) {
@@ -747,6 +754,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (kv_size(virt_lines.data.virt_lines)) {
decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
}
+ if (url != NULL) {
+ DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
+ sh.url = url;
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0);
+ }
if (has_hl) {
DecorSignHighlight sh = decor_sh_from_inline(hl);
decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
@@ -772,7 +784,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
uint32_t decor_indexed = DECOR_ID_INVALID;
+ if (url != NULL) {
+ DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
+ sh.url = url;
+ sh.next = decor_indexed;
+ decor_indexed = decor_put_sh(sh);
+ }
if (sign.flags & kSHIsSign) {
+ sign.next = decor_indexed;
decor_indexed = decor_put_sh(sign);
if (sign.text[0]) {
decor_flags |= MT_FLAG_DECOR_SIGNTEXT;
@@ -814,6 +833,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
error:
clear_virttext(&virt_text.data.virt_text);
clear_virtlines(&virt_lines.data.virt_lines);
+ if (url != NULL) {
+ xfree(url);
+ }
+
return 0;
}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index b2f0039eb9..811f60f4d6 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -54,6 +54,7 @@ typedef struct {
Boolean spell;
Boolean ui_watched;
Boolean undo_restore;
+ String url;
} Dict(set_extmark);
typedef struct {
@@ -183,6 +184,7 @@ typedef struct {
Boolean fg_indexed;
Boolean bg_indexed;
Boolean force;
+ String url;
} Dict(highlight);
typedef struct {
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index b42c274411..f955b315a8 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -784,6 +784,14 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
hlattrs2dict(&rgb, NULL, rgb_attrs, true, false);
hlattrs2dict(&cterm, NULL, rgb_attrs, false, false);
+
+ // URLs are not added in hlattrs2dict since they are used only by UIs and not by the highlight
+ // system. So we add them here.
+ if (rgb_attrs.url >= 0) {
+ const char *url = hl_get_url((uint32_t)rgb_attrs.url);
+ PUT_C(rgb, "url", STRING_OBJ(cstr_as_string((char *)url)));
+ }
+
ADD_C(args, DICTIONARY_OBJ(rgb));
ADD_C(args, DICTIONARY_OBJ(cterm));
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e5a5cc059f..eea9b54a5c 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -176,6 +176,12 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
});
int link_id = -1;
+ // Setting URLs directly through highlight attributes is not supported
+ if (HAS_KEY(val, highlight, url)) {
+ api_free_string(val->url);
+ val->url = NULL_STRING;
+ }
+
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
if (!ERROR_SET(err)) {
ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index d3517c077f..7f1946ba05 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -118,7 +118,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor)
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))) {
if (row2 >= row1) {
redraw_buf_range_later(buf, row1 + 1, row2 + 1);
}
@@ -253,7 +253,7 @@ void decor_free(DecorInline decor)
}
}
-void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
+static void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
{
while (vt) {
if (vt->flags & kVTIsLines) {
@@ -273,6 +273,9 @@ void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
xfree(sh->sign_name);
}
sh->flags = 0;
+ if (sh->url != NULL) {
+ XFREE_CLEAR(sh->url);
+ }
if (sh->next == DECOR_ID_INVALID) {
sh->next = decor_freelist;
decor_freelist = first_idx;
@@ -509,7 +512,8 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end
.draw_col = -10,
};
- if (sh->hl_id || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) {
+ if (sh->hl_id || (sh->url != NULL)
+ || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) {
if (sh->hl_id) {
range.attr_id = syn_id2attr(sh->hl_id);
}
@@ -627,15 +631,22 @@ next_mark:
spell = kFalse;
}
}
+ if (active && item.data.sh.url != NULL) {
+ attr = hl_add_url(attr, item.data.sh.url);
+ }
if (item.start_row == state->row && item.start_col <= col
&& decor_virt_pos(&item) && item.draw_col == -10) {
decor_init_draw_col(win_col, hidden, &item);
}
if (keep) {
kv_A(state->active, j++) = item;
- } else if (item.owned && item.kind == kDecorKindVirtText) {
- clear_virttext(&item.data.vt->data.virt_text);
- xfree(item.data.vt);
+ } else if (item.owned) {
+ if (item.kind == kDecorKindVirtText) {
+ clear_virttext(&item.data.vt->data.virt_text);
+ xfree(item.data.vt);
+ } else if (item.kind == kDecorKindHighlight) {
+ xfree((void *)item.data.sh.url);
+ }
}
}
kv_size(state->active) = j;
@@ -962,6 +973,10 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
PUT(*dict, "ui_watched", BOOLEAN_OBJ(true));
}
+ if (sh_hl.url != NULL) {
+ PUT(*dict, "url", STRING_OBJ(cstr_to_string(sh_hl.url)));
+ }
+
if (virt_text) {
if (virt_text->hl_mode) {
PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode]));
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
index 93bc202b94..8d0075b169 100644
--- a/src/nvim/decoration_defs.h
+++ b/src/nvim/decoration_defs.h
@@ -3,6 +3,7 @@
#include <stdint.h>
#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
#include "nvim/types_defs.h"
#define DECOR_ID_INVALID UINT32_MAX
@@ -68,10 +69,11 @@ typedef struct {
int line_hl_id;
int cursorline_hl_id;
uint32_t next;
+ const char *url;
} DecorSignHighlight;
#define DECOR_SIGN_HIGHLIGHT_INIT { 0, DECOR_PRIORITY_BASE, 0, { 0, 0 }, NULL, 0, 0, 0, 0, \
- DECOR_ID_INVALID }
+ DECOR_ID_INVALID, NULL }
enum {
kVTIsLines = 1,
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index bd599d686f..47a87b90c3 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -42,6 +42,7 @@ static Set(HlEntry) attr_entries = SET_INIT;
static Map(int, int) combine_attr_entries = MAP_INIT;
static Map(int, int) blend_attr_entries = MAP_INIT;
static Map(int, int) blendthrough_attr_entries = MAP_INIT;
+static Set(cstr_t) urls = SET_INIT;
#define attr_entry(i) attr_entries.keys[i]
@@ -475,6 +476,7 @@ int hl_get_underline(void)
.rgb_bg_color = -1,
.rgb_sp_color = -1,
.hl_blend = -1,
+ .url = -1,
},
.kind = kHlUI,
.id1 = 0,
@@ -482,6 +484,43 @@ int hl_get_underline(void)
});
}
+/// Augment an existing attribute with the beginning or end of a URL hyperlink.
+///
+/// @param attr Existing attribute to combine with
+/// @param url The URL to associate with the highlight attribute
+/// @return Combined attribute
+int hl_add_url(int attr, const char *url)
+{
+ HlAttrs attrs = HLATTRS_INIT;
+
+ MHPutStatus status;
+ uint32_t k = set_put_idx(cstr_t, &urls, url, &status);
+ if (status != kMHExisting) {
+ urls.keys[k] = xstrdup(url);
+ }
+
+ attrs.url = (int32_t)k;
+
+ int new = get_attr_entry((HlEntry){
+ .attr = attrs,
+ .kind = kHlUI,
+ .id1 = 0,
+ .id2 = 0,
+ });
+
+ return hl_combine_attr(attr, new);
+}
+
+/// Get a URL by its index.
+///
+/// @param index URL index
+/// @return URL
+const char *hl_get_url(uint32_t index)
+{
+ assert(urls.keys);
+ return urls.keys[index];
+}
+
/// Get attribute code for forwarded :terminal highlights.
int hl_get_term_attr(HlAttrs *aep)
{
@@ -492,12 +531,18 @@ int hl_get_term_attr(HlAttrs *aep)
/// Clear all highlight tables.
void clear_hl_tables(bool reinit)
{
+ const char *url = NULL;
+ set_foreach(&urls, url, {
+ xfree((void *)url);
+ });
+
if (reinit) {
set_clear(HlEntry, &attr_entries);
highlight_init();
map_clear(int, &combine_attr_entries);
map_clear(int, &blend_attr_entries);
map_clear(int, &blendthrough_attr_entries);
+ set_clear(cstr_t, &urls);
memset(highlight_attr_last, -1, sizeof(highlight_attr_last));
highlight_attr_set_all();
highlight_changed();
@@ -508,6 +553,7 @@ void clear_hl_tables(bool reinit)
map_destroy(int, &blend_attr_entries);
map_destroy(int, &blendthrough_attr_entries);
map_destroy(ColorKey, &ns_hls);
+ set_destroy(cstr_t, &urls);
}
}
@@ -599,6 +645,11 @@ int hl_combine_attr(int char_attr, int prim_attr)
new_en.hl_blend = prim_aep.hl_blend;
}
+ if ((new_en.url == -1) && (prim_aep.url >= 0)) {
+ // Combined attributes borrow the string from the primary attribute
+ new_en.url = prim_aep.url;
+ }
+
id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
.id1 = char_attr, .id2 = prim_attr });
if (id > 0) {
@@ -680,8 +731,8 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
}
cattrs.cterm_bg_color = fattrs.cterm_bg_color;
- cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color,
- fattrs.cterm_bg_color);
+ cattrs.cterm_fg_color = (int16_t)cterm_blend(ratio, battrs.cterm_fg_color,
+ fattrs.cterm_bg_color);
cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED);
} else {
cattrs = fattrs;
@@ -729,7 +780,7 @@ static int rgb_blend(int ratio, int rgb1, int rgb2)
return (mr << 16) + (mg << 8) + mb;
}
-static int cterm_blend(int ratio, int c1, int c2)
+static int cterm_blend(int ratio, int16_t c1, int16_t c2)
{
// 1. Convert cterm color numbers to RGB.
// 2. Blend the RGB colors.
@@ -1085,12 +1136,12 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
hlattrs.hl_blend = blend;
- hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1;
- hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1;
+ hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : (int16_t)(ctermbg + 1);
+ hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : (int16_t)(ctermfg + 1);
hlattrs.cterm_ae_attr = cterm_mask;
} else {
- hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1;
- hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1;
+ hlattrs.cterm_bg_color = bg == -1 ? 0 : (int16_t)(bg + 1);
+ hlattrs.cterm_fg_color = fg == -1 ? 0 : (int16_t)(fg + 1);
hlattrs.cterm_ae_attr = mask;
}
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 7a10e16391..25ab9dc2d9 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -3,6 +3,8 @@
#include <stdbool.h>
#include <stdint.h>
+#include "nvim/api/private/defs.h"
+
typedef int32_t RgbValue;
/// Highlighting attribute bits.
@@ -36,8 +38,9 @@ typedef enum {
typedef struct {
int16_t rgb_ae_attr, cterm_ae_attr; ///< HlAttrFlags
RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color;
- int cterm_fg_color, cterm_bg_color;
- int hl_blend;
+ int16_t cterm_fg_color, cterm_bg_color;
+ int32_t hl_blend;
+ int32_t url;
} HlAttrs;
#define HLATTRS_INIT (HlAttrs) { \
@@ -49,6 +52,7 @@ typedef struct {
.cterm_fg_color = 0, \
.cterm_bg_color = 0, \
.hl_blend = -1, \
+ .url = -1, \
}
/// Values for index in highlight_attr[].
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 14c44b0249..ef8181a4cb 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1912,8 +1912,8 @@ static void set_hl_attr(int idx)
HlGroup *sgp = hl_table + idx;
at_en.cterm_ae_attr = (int16_t)sgp->sg_cterm;
- at_en.cterm_fg_color = sgp->sg_cterm_fg;
- at_en.cterm_bg_color = sgp->sg_cterm_bg;
+ at_en.cterm_fg_color = (int16_t)sgp->sg_cterm_fg;
+ at_en.cterm_bg_color = (int16_t)sgp->sg_cterm_bg;
at_en.rgb_ae_attr = (int16_t)sgp->sg_gui;
// FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is
// initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name
diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h
index f3c4e4ea95..836b1447c2 100644
--- a/src/nvim/map_defs.h
+++ b/src/nvim/map_defs.h
@@ -33,7 +33,8 @@ static inline bool equal_String(String a, String b)
if (a.size != b.size) {
return false;
}
- return memcmp(a.data, b.data, a.size) == 0;
+
+ return (a.size == 0) || (memcmp(a.data, b.data, a.size) == 0);
}
#define Set(type) Set_##type
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 8bee5071af..00acbfb602 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -913,8 +913,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg);
bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg);
- int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0);
- int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0);
+ int16_t vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0);
+ int16_t vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0);
bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx - 1];
bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx - 1];
@@ -939,6 +939,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
.rgb_bg_color = vt_bg,
.rgb_sp_color = -1,
.hl_blend = -1,
+ .url = -1,
});
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index f9560ce076..35867d6ce3 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -32,6 +32,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
+#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/terminfo.h"
#include "nvim/tui/tui.h"
@@ -139,6 +140,8 @@ struct TUIData {
int width;
int height;
bool rgb;
+ int url; ///< Index of URL currently being printed, if any
+ StringBuilder urlbuf; ///< Re-usable buffer for writing OSC 8 control sequences
};
static int got_winch = 0;
@@ -147,6 +150,8 @@ static bool cursor_style_enabled = false;
# include "tui/tui.c.generated.h"
#endif
+static Set(cstr_t) urls = SET_INIT;
+
void tui_start(TUIData **tui_p, int *width, int *height, char **term, bool *rgb)
FUNC_ATTR_NONNULL_ALL
{
@@ -156,7 +161,9 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term, bool *rgb)
tui->stopped = false;
tui->seen_error_exit = 0;
tui->loop = &main_loop;
+ tui->url = -1;
kv_init(tui->invalid_regions);
+ kv_init(tui->urlbuf);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
// TODO(bfredl): zero hl is empty, send this explicitly?
@@ -412,7 +419,7 @@ static void terminfo_start(TUIData *tui)
static void terminfo_stop(TUIData *tui)
{
// Destroy output stuff
- tui_mode_change(tui, (String)STRING_INIT, SHAPE_IDX_N);
+ tui_mode_change(tui, NULL_STRING, SHAPE_IDX_N);
tui_mouse_off(tui);
unibi_out(tui, unibi_exit_attribute_mode);
// Reset cursor to normal before exiting alternate screen.
@@ -423,7 +430,7 @@ static void terminfo_stop(TUIData *tui)
tui_reset_key_encoding(tui);
// May restore old title before exiting alternate screen.
- tui_set_title(tui, (String)STRING_INIT);
+ tui_set_title(tui, NULL_STRING);
if (ui_client_exit_status == 0) {
ui_client_exit_status = tui->seen_error_exit;
}
@@ -522,7 +529,15 @@ void tui_free_all_mem(TUIData *tui)
{
ugrid_free(&tui->grid);
kv_destroy(tui->invalid_regions);
+
+ const char *url;
+ set_foreach(&urls, url, {
+ xfree((void *)url);
+ });
+ set_destroy(cstr_t, &urls);
+
kv_destroy(tui->attrs);
+ kv_destroy(tui->urlbuf);
xfree(tui->space_buf);
xfree(tui->term);
xfree(tui);
@@ -550,6 +565,10 @@ static bool attrs_differ(TUIData *tui, int id1, int id2, bool rgb)
HlAttrs a1 = kv_A(tui->attrs, (size_t)id1);
HlAttrs a2 = kv_A(tui->attrs, (size_t)id2);
+ if (a1.url != a2.url) {
+ return true;
+ }
+
if (rgb) {
return a1.rgb_fg_color != a2.rgb_fg_color
|| a1.rgb_bg_color != a2.rgb_bg_color
@@ -709,6 +728,19 @@ static void update_attrs(TUIData *tui, int attr_id)
}
}
+ if (tui->url != attrs.url) {
+ if (attrs.url >= 0) {
+ const char *url = urls.keys[attrs.url];
+ kv_size(tui->urlbuf) = 0;
+ kv_printf(tui->urlbuf, "\x1b]8;;%s\x1b\\", url);
+ out(tui, tui->urlbuf.items, kv_size(tui->urlbuf));
+ } else {
+ out(tui, S_LEN("\x1b]8;;\x1b\\"));
+ }
+
+ tui->url = attrs.url;
+ }
+
tui->default_attr = fg == -1 && bg == -1
&& !bold && !italic && !has_any_underline && !reverse && !standout
&& !strikethrough;
@@ -785,6 +817,13 @@ static void cursor_goto(TUIData *tui, int row, int col)
if (row == grid->row && col == grid->col) {
return;
}
+
+ // If an OSC 8 sequence is active terminate it before moving the cursor
+ if (tui->url >= 0) {
+ out(tui, S_LEN("\x1b]8;;\x1b\\"));
+ tui->url = -1;
+ }
+
if (0 == row && 0 == col) {
unibi_out(tui, unibi_cursor_home);
ugrid_goto(grid, row, col);
@@ -1281,11 +1320,32 @@ void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow,
}
}
+/// Add a URL to be used in an OSC 8 hyperlink.
+///
+/// @param tui TUIData
+/// @param url URL to add
+/// @return Index of new URL, or -1 if URL is invalid
+int32_t tui_add_url(TUIData *tui, const char *url)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (url == NULL) {
+ return -1;
+ }
+
+ MHPutStatus status;
+ uint32_t k = set_put_idx(cstr_t, &urls, url, &status);
+ if (status != kMHExisting) {
+ urls.keys[k] = xstrdup(url);
+ }
+ return (int32_t)k;
+}
+
void tui_hl_attr_define(TUIData *tui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info)
{
attrs.cterm_ae_attr = cterm_attrs.cterm_ae_attr;
attrs.cterm_fg_color = cterm_attrs.cterm_fg_color;
attrs.cterm_bg_color = cterm_attrs.cterm_bg_color;
+
kv_a(tui->attrs, (size_t)id) = attrs;
}
@@ -1302,11 +1362,11 @@ void tui_visual_bell(TUIData *tui)
void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{
- tui->clear_attrs.rgb_fg_color = (int)rgb_fg;
- tui->clear_attrs.rgb_bg_color = (int)rgb_bg;
- tui->clear_attrs.rgb_sp_color = (int)rgb_sp;
- tui->clear_attrs.cterm_fg_color = (int)cterm_fg;
- tui->clear_attrs.cterm_bg_color = (int)cterm_bg;
+ tui->clear_attrs.rgb_fg_color = (RgbValue)rgb_fg;
+ tui->clear_attrs.rgb_bg_color = (RgbValue)rgb_bg;
+ tui->clear_attrs.rgb_sp_color = (RgbValue)rgb_sp;
+ tui->clear_attrs.cterm_fg_color = (int16_t)cterm_fg;
+ tui->clear_attrs.cterm_bg_color = (int16_t)cterm_bg;
tui->print_attr_id = -1;
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 055e234d67..d4dec7db83 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -175,7 +175,14 @@ static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb)
// TODO(bfredl): log "err"
return HLATTRS_INIT;
}
- return dict2hlattrs(&dict, rgb, NULL, &err);
+
+ HlAttrs attrs = dict2hlattrs(&dict, rgb, NULL, &err);
+
+ if (HAS_KEY(&dict, highlight, url)) {
+ attrs.url = tui_add_url(tui, dict.url.data);
+ }
+
+ return attrs;
}
void ui_client_event_grid_resize(Array args)