test(messages/cmdline_spec): convert highlight IDs to name and format (#34845)

Problem:  Hardcoded highlight IDs for ext_messages/cmdline output need
          to be adjusted everytime a builtin highlight group is added.
Solution: Store a global map of default highlights through nvim_get_hl()
          and fetch missing (custom) highlight groups through synIDattr().
          Use more compact formatting for screen:expect().
This commit is contained in:
luukvbaal
2025-07-09 11:33:19 +02:00
committed by GitHub
parent 3c9484b550
commit 3a3484be29
6 changed files with 1037 additions and 1631 deletions

View File

@@ -749,8 +749,8 @@ This UI extension delegates presentation of the |cmdline| (except 'wildmenu').
For command-line 'wildmenu' UI events, activate |ui-popupmenu|. For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
["cmdline_show", content, pos, firstc, prompt, indent, level, hl_id] ~ ["cmdline_show", content, pos, firstc, prompt, indent, level, hl_id] ~
content: List of [attrs, string] content: List of [attrs, string, hl_id]
[[{}, "t"], [attrs, "est"], ...] [[{}, "t", hl_id], [attrs, "est", hl_id], ...]
Triggered when the cmdline is displayed or changed. Triggered when the cmdline is displayed or changed.
The `content` is the full content that should be displayed in the The `content` is the full content that should be displayed in the

View File

@@ -3503,26 +3503,29 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
} }
char *buf = arena_alloc(&arena, len, false); char *buf = arena_alloc(&arena, len, false);
memset(buf, '*', len); memset(buf, '*', len);
Array item = arena_array(&arena, 2); Array item = arena_array(&arena, 3);
ADD_C(item, INTEGER_OBJ(0)); ADD_C(item, INTEGER_OBJ(0));
ADD_C(item, STRING_OBJ(cbuf_as_string(buf, len))); ADD_C(item, STRING_OBJ(cbuf_as_string(buf, len)));
ADD_C(item, INTEGER_OBJ(0));
ADD_C(content, ARRAY_OBJ(item)); ADD_C(content, ARRAY_OBJ(item));
} else if (kv_size(line->last_colors.colors)) { } else if (kv_size(line->last_colors.colors)) {
content = arena_array(&arena, kv_size(line->last_colors.colors)); content = arena_array(&arena, kv_size(line->last_colors.colors));
for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) { for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i); CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i);
Array item = arena_array(&arena, 2); Array item = arena_array(&arena, 3);
ADD_C(item, INTEGER_OBJ(chunk.hl_id == 0 ? 0 : syn_id2attr(chunk.hl_id))); ADD_C(item, INTEGER_OBJ(chunk.hl_id == 0 ? 0 : syn_id2attr(chunk.hl_id)));
assert(chunk.end >= chunk.start); assert(chunk.end >= chunk.start);
ADD_C(item, STRING_OBJ(cbuf_as_string(line->cmdbuff + chunk.start, ADD_C(item, STRING_OBJ(cbuf_as_string(line->cmdbuff + chunk.start,
(size_t)(chunk.end - chunk.start)))); (size_t)(chunk.end - chunk.start))));
ADD_C(item, INTEGER_OBJ(chunk.hl_id));
ADD_C(content, ARRAY_OBJ(item)); ADD_C(content, ARRAY_OBJ(item));
} }
} else { } else {
Array item = arena_array(&arena, 2); Array item = arena_array(&arena, 3);
ADD_C(item, INTEGER_OBJ(0)); ADD_C(item, INTEGER_OBJ(0));
ADD_C(item, CSTR_AS_OBJ(line->cmdbuff)); ADD_C(item, CSTR_AS_OBJ(line->cmdbuff));
ADD_C(item, INTEGER_OBJ(0));
content = arena_array(&arena, 1); content = arena_array(&arena, 1);
ADD_C(content, ARRAY_OBJ(item)); ADD_C(content, ARRAY_OBJ(item));
} }
@@ -3549,6 +3552,7 @@ void ui_ext_cmdline_block_append(size_t indent, const char *line)
Array item = ARRAY_DICT_INIT; Array item = ARRAY_DICT_INIT;
ADD(item, INTEGER_OBJ(0)); ADD(item, INTEGER_OBJ(0));
ADD(item, CSTR_AS_OBJ(buf)); ADD(item, CSTR_AS_OBJ(buf));
ADD(item, INTEGER_OBJ(0));
Array content = ARRAY_DICT_INIT; Array content = ARRAY_DICT_INIT;
ADD(content, ARRAY_OBJ(item)); ADD(content, ARRAY_OBJ(item));
ADD(cmdline_block, ARRAY_OBJ(content)); ADD(cmdline_block, ARRAY_OBJ(content));

View File

@@ -191,11 +191,7 @@ describe('vim.ui_attach', function()
^1 | ^1 |
{1:~ }|*4 {1:~ }|*4
]], ]],
cmdline = { { cmdline = { { content = { { '' } }, firstc = ':', pos = 0 } },
content = { { '' } },
firstc = ':',
pos = 0,
} },
}) })
feed('version<CR>') feed('version<CR>')
screen:expect({ screen:expect({
@@ -203,7 +199,6 @@ describe('vim.ui_attach', function()
^2 | ^2 |
{1:~ }|*4 {1:~ }|*4
]], ]],
cmdline = { { abort = false } },
condition = function() condition = function()
eq('list_cmd', screen.messages[1].kind) eq('list_cmd', screen.messages[1].kind)
screen.messages = {} -- Ignore the build dependent :version content screen.messages = {} -- Ignore the build dependent :version content
@@ -216,28 +211,12 @@ describe('vim.ui_attach', function()
{1:~ }|*4 {1:~ }|*4
]], ]],
cmdline = { cmdline = {
{ { content = { { '' } }, hl = 'MoreMsg', pos = 0, prompt = '[Y]es, (N)o, (C)ancel: ' },
content = { { '' } },
hl_id = 10,
pos = 0,
prompt = '[Y]es, (N)o, (C)ancel: ',
},
},
messages = {
{
content = { { '\nSave changes?\n', 6, 10 } },
kind = 'confirm',
},
}, },
messages = { { content = { { '\nSave changes?\n', 6, 'MoreMsg' } }, kind = 'confirm' } },
}) })
feed('n') feed('n')
screen:expect({ screen:expect_unchanged()
grid = [[
^4 |
{1:~ }|*4
]],
cmdline = { { abort = false } },
})
end) end)
it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function() it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function()
@@ -257,37 +236,31 @@ describe('vim.ui_attach', function()
]]) ]])
-- Updates a cmdline window -- Updates a cmdline window
feed(':cmdline') feed(':cmdline')
screen:expect({ screen:expect([[
grid = [[ cmdline |
cmdline | {2:cmdline [+] }|
{2:cmdline [+] }| fooba^r |
fooba^r | {3:[No Name] [+] }|
{3:[No Name] [+] }| |
| ]])
]],
})
-- Does not clear 'incsearch' highlighting -- Does not clear 'incsearch' highlighting
feed('<Esc>/foo') feed('<Esc>/foo')
screen:expect({ screen:expect([[
grid = [[ foo |
foo | {2:cmdline [+] }|
{2:cmdline [+] }| {2:foo}ba^r |
{2:foo}ba^r | {3:[No Name] [+] }|
{3:[No Name] [+] }| |
| ]])
]],
})
-- Shows new cmdline state during 'inccommand' -- Shows new cmdline state during 'inccommand'
feed('<Esc>:%s/bar/baz') feed('<Esc>:%s/bar/baz')
screen:expect({ screen:expect([[
grid = [[ %s/bar/baz |
%s/bar/baz | {2:cmdline [+] }|
{2:cmdline [+] }| foo{10:ba^z} |
foo{10:ba^z} | {3:[No Name] [+] }|
{3:[No Name] [+] }| |
| ]])
]],
})
end) end)
it('msg_show in fast context', function() it('msg_show in fast context', function()
@@ -314,10 +287,9 @@ describe('vim.ui_attach', function()
lled in a fast event context | lled in a fast event context |
{1:~ }| {1:~ }|
]], ]],
cmdline = { { abort = false } },
messages = { messages = {
{ {
content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } }, content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 'ErrorMsg' } },
history = true, history = true,
kind = 'emsg', kind = 'emsg',
}, },

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,8 @@ end
--- @field cmdline table<integer,table> --- @field cmdline table<integer,table>
--- @field cmdline_hide_level integer? --- @field cmdline_hide_level integer?
--- @field cmdline_block table[] --- @field cmdline_block table[]
--- @field hl_groups table<string,integer> --- @field hl_groups table<string,integer> Highlight group to attr ID map
--- @field hl_names table<integer,string> Highlight ID to group map
--- @field messages table<integer,table> --- @field messages table<integer,table>
--- @field private _cursor {grid:integer,row:integer,col:integer} --- @field private _cursor {grid:integer,row:integer,col:integer}
--- @field private _grids table<integer,test.functional.ui.screen.Grid> --- @field private _grids table<integer,test.functional.ui.screen.Grid>
@@ -154,6 +155,11 @@ local function _init_colors()
[29] = { foreground = Screen.colors.SlateBlue, bold = true }, [29] = { foreground = Screen.colors.SlateBlue, bold = true },
[30] = { background = Screen.colors.Red }, [30] = { background = Screen.colors.Red },
} }
Screen._global_hl_names = {}
for group in pairs(n.api.nvim_get_hl(0, {})) do
Screen._global_hl_names[n.api.nvim_get_hl_id_by_name(group)] = group
end
end end
--- @class test.functional.ui.screen.Opts --- @class test.functional.ui.screen.Opts
@@ -210,6 +216,7 @@ function Screen.new(width, height, options, session)
showcmd = {}, showcmd = {},
ruler = {}, ruler = {},
hl_groups = {}, hl_groups = {},
hl_names = vim.deepcopy(Screen._global_hl_names),
_default_attr_ids = nil, _default_attr_ids = nil,
mouse_enabled = true, mouse_enabled = true,
_attrs = {}, _attrs = {},
@@ -1343,12 +1350,12 @@ function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level
firstc = firstc, firstc = firstc,
prompt = prompt, prompt = prompt,
indent = indent, indent = indent,
hl_id = prompt and hl_id, hl = hl_id,
} }
end end
function Screen:_handle_cmdline_hide(level, abort) function Screen:_handle_cmdline_hide(level, abort)
self.cmdline[level] = { abort = abort } self.cmdline[level] = abort and { abort = abort } or nil
self.cmdline_hide_level = level self.cmdline_hide_level = level
end end
@@ -1485,6 +1492,13 @@ function Screen:_row_repr(gridnr, rownr, attr_state, cursor)
return table.concat(rv, '') --:gsub('%s+$', '') return table.concat(rv, '') --:gsub('%s+$', '')
end end
local function hl_id_to_name(self, id)
if id and id > 0 and not self.hl_names[id] then
self.hl_names[id] = n.fn.synIDattr(id, 'name')
end
return id and self.hl_names[id] or nil
end
function Screen:_extstate_repr(attr_state) function Screen:_extstate_repr(attr_state)
local cmdline = {} local cmdline = {}
for i, entry in pairs(self.cmdline) do for i, entry in pairs(self.cmdline) do
@@ -1492,6 +1506,7 @@ function Screen:_extstate_repr(attr_state)
if entry.content ~= nil then if entry.content ~= nil then
entry.content = self:_chunks_repr(entry.content, attr_state) entry.content = self:_chunks_repr(entry.content, attr_state)
end end
entry.hl = hl_id_to_name(self, entry.hl)
cmdline[i] = entry cmdline[i] = entry
end end
@@ -1552,7 +1567,8 @@ function Screen:_chunks_repr(chunks, attr_state)
attrs = hl attrs = hl
end end
local attr_id = self:_get_attr_id(attr_state, attrs, hl) local attr_id = self:_get_attr_id(attr_state, attrs, hl)
repr_chunks[i] = { text, attr_id, attr_id and id or nil } repr_chunks[i] = { text, attr_id }
repr_chunks[i][#repr_chunks[i] + 1] = hl_id_to_name(self, id)
end end
return repr_chunks return repr_chunks
end end