feat(extmark): stack multiple highlight groups in hl_group

This has been possible in the "backend" for a while but
API was missing.

Followup: we will need a `details2=true` mode for `nvim_get_hl_id_by_name`
to return information in a way forward compatible with even further
enhancements.
This commit is contained in:
bfredl 2025-01-17 13:44:07 +01:00
parent 5dd60e01ac
commit 4cced601c8
7 changed files with 97 additions and 4 deletions

View File

@ -2660,6 +2660,8 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
and below highlight groups can be supplied either as a
string or as an integer, the latter of which can be
obtained using |nvim_get_hl_id_by_name()|.
Multiple highlight groups can be stacked by passing an
array (highest priority last).
• hl_eol : when true, for a multiline highlight covering the
EOL of a line, continue the highlight for the rest of the
screen line (just like for diff and cursorline highlight).

View File

@ -188,6 +188,7 @@ API
• |nvim_echo()| `err` field to print error messages and `chunks` accepts
highlight group IDs.
• |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline".
• |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups
DEFAULTS

View File

@ -595,6 +595,9 @@ function vim.api.nvim_buf_line_count(buffer) end
--- - hl_group : highlight group used for the text range. This and below
--- highlight groups can be supplied either as a string or as an integer,
--- the latter of which can be obtained using `nvim_get_hl_id_by_name()`.
---
--- Multiple highlight groups can be stacked by passing an array (highest
--- priority last).
--- - hl_eol : when true, for a multiline highlight covering the
--- EOL of a line, continue the highlight for the rest
--- of the screen line (just like for diff and

View File

@ -241,7 +241,7 @@ error('Cannot require a meta file')
--- @field end_line? integer
--- @field end_row? integer
--- @field end_col? integer
--- @field hl_group? integer|string
--- @field hl_group? any
--- @field virt_text? any[]
--- @field virt_text_pos? string
--- @field virt_text_win_col? integer

View File

@ -385,6 +385,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// - hl_group : highlight group used for the text range. This and below
/// highlight groups can be supplied either as a string or as an integer,
/// the latter of which can be obtained using |nvim_get_hl_id_by_name()|.
///
/// Multiple highlight groups can be stacked by passing an array (highest
/// priority last).
/// - hl_eol : when true, for a multiline highlight covering the
/// EOL of a line, continue the highlight for the rest
/// of the screen line (just like for diff and
@ -499,6 +502,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT;
char *url = NULL;
bool has_hl = false;
bool has_hl_multiple = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
@ -551,8 +555,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
col2 = (int)val;
}
hl.hl_id = (int)opts->hl_group;
has_hl = hl.hl_id > 0;
if (HAS_KEY(opts, set_extmark, hl_group)) {
if (opts->hl_group.type == kObjectTypeArray) {
Array arr = opts->hl_group.data.array;
if (arr.size >= 1) {
hl.hl_id = object_to_hl_id(arr.items[0], "hl_group item", err);
if (ERROR_SET(err)) {
goto error;
}
}
for (size_t i = 1; i < arr.size; i++) {
int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err);
if (ERROR_SET(err)) {
goto error;
}
if (hl_id) {
has_hl_multiple = true;
}
}
} else {
hl.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err);
if (ERROR_SET(err)) {
goto error;
}
}
has_hl = hl.hl_id > 0;
}
sign.hl_id = (int)opts->sign_hl_group;
sign.cursorline_hl_id = (int)opts->cursorline_hl_group;
sign.number_hl_id = (int)opts->number_hl_group;
@ -794,6 +823,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
}
if (has_hl_multiple) {
Array arr = opts->hl_group.data.array;
for (size_t i = arr.size - 1; i > 0; i--) { // skip hl_group[0], handled as hl.hl_id below
int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err);
if (hl_id > 0) {
DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
sh.hl_id = hl_id;
sh.flags = opts->hl_eol ? kSHHlEol : 0;
sh.next = decor_indexed;
decor_indexed = decor_put_sh(sh);
decor_flags |= MT_FLAG_DECOR_HL;
}
}
}
DecorInline decor = DECOR_INLINE_INIT;
if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL
|| schar_high(hl.conceal_char)) {

View File

@ -29,7 +29,7 @@ typedef struct {
Integer end_line;
Integer end_row;
Integer end_col;
HLGroupID hl_group;
Object hl_group;
Array virt_text;
String virt_text_pos;
Integer virt_text_win_col;

View File

@ -834,6 +834,9 @@ describe('extmark decorations', function()
[42] = {undercurl = true, special = Screen.colors.Red};
[43] = {background = Screen.colors.Yellow, undercurl = true, special = Screen.colors.Red};
[44] = {background = Screen.colors.LightMagenta};
[45] = { background = Screen.colors.Red, special = Screen.colors.Red, foreground = Screen.colors.Red };
[46] = { background = Screen.colors.Blue, foreground = Screen.colors.Blue, special = Screen.colors.Red };
[47] = { background = Screen.colors.Green, foreground = Screen.colors.Blue, special = Screen.colors.Red };
}
ns = api.nvim_create_namespace 'test'
@ -1924,6 +1927,46 @@ describe('extmark decorations', function()
]]}
end)
it('highlight can combine multiple groups', function()
screen:try_resize(50, 3)
command('hi Group1 guibg=Red guifg=Red guisp=Red')
command('hi Group2 guibg=Blue guifg=Blue')
command('hi Group3 guibg=Green')
insert([[example text]])
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {} })
screen:expect([[
example tex^t |
{1:~ }|
|
]])
api.nvim_buf_clear_namespace(0, ns, 0, -1)
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {'Group1'} })
screen:expect([[
{45:example tex^t} |
{1:~ }|
|
]])
api.nvim_buf_clear_namespace(0, ns, 0, -1)
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2'} })
screen:expect([[
{46:example tex^t} |
{1:~ }|
|
]])
api.nvim_buf_clear_namespace(0, ns, 0, -1)
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', 'Group3'}, hl_eol=true })
screen:expect([[
{47:example tex^t }|
{1:~ }|
|
]])
eq('Invalid hl_group: hl_group item',
pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', {'fail'}}, hl_eol=true }))
end)
it('highlight works after TAB with sidescroll #14201', function()
screen:try_resize(50, 3)
command('set nowrap')