From 1495d36d63305862da3c4106455667d51b578707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Linse?= Date: Fri, 23 Jul 2021 18:00:42 +0200 Subject: [PATCH] feat(decorations): allow more than one stacked highlight in a virt_text --- src/nvim/api/buffer.c | 10 ++++-- src/nvim/api/private/helpers.c | 28 +++++++++++---- src/nvim/screen.c | 20 +++++++---- test/functional/ui/decorations_spec.lua | 46 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 78e36e5ef0..47216996b4 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1433,8 +1433,14 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - hl_group : name of the highlight group used to highlight /// this mark. /// - virt_text : virtual text to link to this mark. -/// - virt_text_pos : positioning of virtual text. Possible -/// values: +/// A list of [text, highlight] tuples, each representing a +/// text chunk with specified highlight. `highlight` element +/// can either be a a single highlight group, or an array of +/// multiple highlight groups that will be stacked +/// (highest priority last). A highlight group can be supplied +/// either as a string or as an integer, the latter which +/// can be obtained using |nvim_get_hl_id_by_name|. +/// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without /// shifting the underlying text. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 9f2e94f31e..e78bd2ea9a 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1603,22 +1603,38 @@ VirtText parse_virt_text(Array chunks, Error *err) Array chunk = chunks.items[i].data.array; if (chunk.size == 0 || chunk.size > 2 || chunk.items[0].type != kObjectTypeString - || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + || chunk.size > 2) { api_set_error(err, kErrorTypeValidation, "Chunk is not an array with one or two strings"); goto free_exit; } String str = chunk.items[0].data.string; - char *text = transstr(str.size > 0 ? str.data : ""); // allocates int hl_id = 0; if (chunk.size == 2) { - String hl = chunk.items[1].data.string; - if (hl.size > 0) { - hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + Object hl = chunk.items[1]; + if (hl.type == kObjectTypeArray) { + Array arr = hl.data.array; + for (size_t j = 0; j < arr.size; j++) { + hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err); + if (ERROR_SET(err)) { + goto free_exit; + } + if (j < arr.size-1) { + kv_push(virt_text, ((VirtTextChunk){ .text = NULL, + .hl_id = hl_id })); + } + } + } else { + hl_id = object_to_hl_id(hl, "virt_text highlight", err); + if (ERROR_SET(err)) { + goto free_exit; + } } } + + char *text = transstr(str.size > 0 ? str.data : ""); // allocates kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); } @@ -1656,7 +1672,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) String str = obj.data.string; return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; } else if (obj.type == kObjectTypeInteger) { - return (int)obj.data.integer; + return MAX((int)obj.data.integer, 0); } else { api_set_error(err, kErrorTypeValidation, "%s is not a valid highlight", what); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ae785132bb..586aeff1e2 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -4021,6 +4021,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, while (wp->w_p_rl ? col >= 0 : col < grid->Columns) { int cells = -1; + // TODO: integrate this whith the other virt_text impl already if (do_virttext && !delay_virttext) { if (*s.p == NUL) { if (virt_pos < virt_text.size) { @@ -4424,14 +4425,21 @@ void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col) while (col < max_col) { if (!*s.p) { - if (virt_pos == kv_size(vt)) { + if (virt_pos >= kv_size(vt)) { + break; + } + virt_attr = 0; + do { + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_combine_attr(virt_attr, + hl_id > 0 ? syn_id2attr(hl_id) : 0); + virt_pos++; + } while (!s.p && virt_pos < kv_size(vt)); + // TODO: y w0t m8 + if (!s.p) { break; } - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; - virt_pos++; - continue; } int attr; bool through = false; diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 0e4b3574e9..96d9eab065 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -697,6 +697,52 @@ end]] | ]]} end) + + it('can have virtual text which combines foreground and backround groups', function() + screen:set_default_attr_ids { + [1] = {bold=true, foreground=Screen.colors.Blue}; + [2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')}; + [3] = {background = tonumber('0x123456'), foreground = tonumber('0xcccccc')}; + [4] = {background = tonumber('0x234567'), foreground = tonumber('0xbbbbbb')}; + [5] = {background = tonumber('0x234567'), foreground = tonumber('0xcccccc')}; + [6] = {bold = true, foreground = tonumber('0xcccccc'), background = tonumber('0x234567')}; + } + + exec [[ + hi BgOne guibg=#123456 + hi BgTwo guibg=#234567 + hi FgEin guifg=#bbbbbb + hi FgZwei guifg=#cccccc + hi VeryBold gui=bold + ]] + + meths.buf_set_extmark(0, ns, 0, 0, { virt_text={ + {'a', {'BgOne', 'FgEin'}}; + {'b', {'BgOne', 'FgZwei'}}; + {'c', {'BgTwo', 'FgEin'}}; + {'d', {'BgTwo', 'FgZwei'}}; + {'X', {'BgTwo', 'FgZwei', 'VeryBold'}}; + }}) + + screen:expect{grid=[[ + ^ {2:a}{3:b}{4:c}{5:d}{6:X} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) + it('does not crash when deleting a cleared buffer #15212', function() exec_lua [[ ns = vim.api.nvim_create_namespace("myplugin")