fix(api): nvim_get_hl drops groups defined with link_global #38492

Problem: hlgroup2dict passes &ns_id to ns_get_hl twice. The first call
(link=true) sets *ns_hl = 0 when link_global is set, so the second call
and the sg_cleared guard both see ns_id == 0 and bail out. The group is
silently dropped from the result.

Solution: use a temporary copy of ns_id for each ns_get_hl call so the
original value is preserved.

(cherry picked from commit 49086862fc)
This commit is contained in:
glepnir
2026-04-12 20:38:35 +08:00
committed by github-actions[bot]
parent 452a9b895c
commit 4053141cb3
8 changed files with 57 additions and 35 deletions
+12 -10
View File
@@ -1555,10 +1555,15 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
• {name} (`string`) Highlight group name, e.g. "ErrorMsg"
• {val} (`vim.api.keyset.highlight`) Highlight definition map,
accepts the following keys:
• altfont: boolean
• bg: color name or "#RRGGBB", see note.
• bg_indexed: boolean (default false) If true, bg is a
terminal palette index (0-255).
• blend: integer between 0 and 100
• blink: boolean
• bold: boolean
• conceal: boolean Concealment at the UI level (terminal
SGR), unrelated to |:syn-conceal|.
• cterm: cterm attribute map, like |highlight-args|. If not
set, cterm attributes will match those from the attribute
map documented above.
@@ -1566,25 +1571,20 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
• ctermfg: Sets foreground of cterm color |ctermfg|
• default: boolean Don't override existing definition
|:hi-default|
• dim: boolean
• fg: color name or "#RRGGBB", see note.
• fg_indexed: boolean (default false) If true, fg is a
terminal palette index (0-255).
• force: if true force update the highlight group when it
exists.
• link: Name of highlight group to link to. |:hi-link|
• sp: color name or "#RRGGBB"
• update: boolean (default false) Update specified attributes
only, leave others unchanged.
• altfont: boolean
• blink: boolean
• bold: boolean
• conceal: boolean Concealment at the UI level (terminal
SGR), unrelated to |:syn-conceal|.
• dim: boolean
• italic: boolean
• link: Name of highlight group to link to. |:hi-link|
• link_global: Like "link", but always resolved in the global
(ns=0) namespace.
• nocombine: boolean
• overline: boolean
• reverse: boolean
• sp: color name or "#RRGGBB"
• standout: boolean
• strikethrough: boolean
• undercurl: boolean
@@ -1592,6 +1592,8 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
• underdotted: boolean
• underdouble: boolean
• underline: boolean
• update: boolean (default false) Update specified attributes
only, leave others unchanged.
nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()*
Set active namespace for highlights defined with |nvim_set_hl()|. This can
+9 -8
View File
@@ -2225,29 +2225,29 @@ function vim.api.nvim_set_decoration_provider(ns_id, opts) end
--- `nvim_set_hl_ns()` or `nvim_win_set_hl_ns()` to activate them.
--- @param name string Highlight group name, e.g. "ErrorMsg"
--- @param val vim.api.keyset.highlight Highlight definition map, accepts the following keys:
--- - altfont: boolean
--- - bg: color name or "#RRGGBB", see note.
--- - bg_indexed: boolean (default false) If true, bg is a terminal palette index (0-255).
--- - blend: integer between 0 and 100
--- - blink: boolean
--- - bold: boolean
--- - conceal: boolean Concealment at the UI level (terminal SGR), unrelated to `:syn-conceal`.
--- - cterm: cterm attribute map, like `highlight-args`. If not set, cterm attributes
--- will match those from the attribute map documented above.
--- - ctermbg: Sets background of cterm color `ctermbg`
--- - ctermfg: Sets foreground of cterm color `ctermfg`
--- - default: boolean Don't override existing definition `:hi-default`
--- - dim: boolean
--- - fg: color name or "#RRGGBB", see note.
--- - fg_indexed: boolean (default false) If true, fg is a terminal palette index (0-255).
--- - force: if true force update the highlight group when it exists.
--- - link: Name of highlight group to link to. `:hi-link`
--- - sp: color name or "#RRGGBB"
--- - update: boolean (default false) Update specified attributes only, leave others unchanged.
--- - altfont: boolean
--- - blink: boolean
--- - bold: boolean
--- - conceal: boolean Concealment at the UI level (terminal SGR), unrelated to `:syn-conceal`.
--- - dim: boolean
--- - italic: boolean
--- - link: Name of highlight group to link to. `:hi-link`
--- - link_global: Like "link", but always resolved in the global (ns=0) namespace.
--- - nocombine: boolean
--- - overline: boolean
--- - reverse: boolean
--- - sp: color name or "#RRGGBB"
--- - standout: boolean
--- - strikethrough: boolean
--- - undercurl: boolean
@@ -2255,6 +2255,7 @@ function vim.api.nvim_set_decoration_provider(ns_id, opts) end
--- - underdotted: boolean
--- - underdouble: boolean
--- - underline: boolean
--- - update: boolean (default false) Update specified attributes only, leave others unchanged.
function vim.api.nvim_set_hl(ns_id, name, val) end
--- Set active namespace for highlights defined with `nvim_set_hl()`. This can be set for
+1 -1
View File
@@ -329,7 +329,7 @@ error('Cannot require a meta file')
--- @field special? integer|string
--- @field sp? integer|string
--- @field link? integer|string
--- @field global_link? integer|string
--- @field link_global? integer|string
--- @field fallback? boolean
--- @field blend? integer
--- @field fg_indexed? boolean
+1 -1
View File
@@ -200,7 +200,7 @@ typedef struct {
Union(Integer, String) special;
Union(Integer, String) sp;
HLGroupID link;
HLGroupID global_link;
HLGroupID link_global;
Boolean fallback;
Integer blend;
Boolean fg_indexed;
+9 -8
View File
@@ -141,29 +141,29 @@ DictAs(get_hl_info) nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena
/// |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to activate them.
/// @param name Highlight group name, e.g. "ErrorMsg"
/// @param val Highlight definition map, accepts the following keys:
/// - altfont: boolean
/// - bg: color name or "#RRGGBB", see note.
/// - bg_indexed: boolean (default false) If true, bg is a terminal palette index (0-255).
/// - blend: integer between 0 and 100
/// - blink: boolean
/// - bold: boolean
/// - conceal: boolean Concealment at the UI level (terminal SGR), unrelated to |:syn-conceal|.
/// - cterm: cterm attribute map, like |highlight-args|. If not set, cterm attributes
/// will match those from the attribute map documented above.
/// - ctermbg: Sets background of cterm color |ctermbg|
/// - ctermfg: Sets foreground of cterm color |ctermfg|
/// - default: boolean Don't override existing definition |:hi-default|
/// - dim: boolean
/// - fg: color name or "#RRGGBB", see note.
/// - fg_indexed: boolean (default false) If true, fg is a terminal palette index (0-255).
/// - force: if true force update the highlight group when it exists.
/// - link: Name of highlight group to link to. |:hi-link|
/// - sp: color name or "#RRGGBB"
/// - update: boolean (default false) Update specified attributes only, leave others unchanged.
/// - altfont: boolean
/// - blink: boolean
/// - bold: boolean
/// - conceal: boolean Concealment at the UI level (terminal SGR), unrelated to |:syn-conceal|.
/// - dim: boolean
/// - italic: boolean
/// - link: Name of highlight group to link to. |:hi-link|
/// - link_global: Like "link", but always resolved in the global (ns=0) namespace.
/// - nocombine: boolean
/// - overline: boolean
/// - reverse: boolean
/// - sp: color name or "#RRGGBB"
/// - standout: boolean
/// - strikethrough: boolean
/// - undercurl: boolean
@@ -171,6 +171,7 @@ DictAs(get_hl_info) nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena
/// - underdotted: boolean
/// - underdouble: boolean
/// - underline: boolean
/// - update: boolean (default false) Update specified attributes only, leave others unchanged.
/// @param[out] err Error details, if any
void nvim_set_hl(uint64_t channel_id, Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
+4 -4
View File
@@ -1105,14 +1105,14 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, HlAttrs
blend = (int)blend0;
}
if (HAS_KEY_X(dict, link) || HAS_KEY_X(dict, global_link)) {
if (HAS_KEY_X(dict, link) || HAS_KEY_X(dict, link_global)) {
if (!link_id) {
api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'",
HAS_KEY_X(dict, global_link) ? "global_link" : "link");
HAS_KEY_X(dict, link_global) ? "link_global" : "link");
return hlattrs;
}
if (HAS_KEY_X(dict, global_link)) {
*link_id = (int)dict->global_link;
if (HAS_KEY_X(dict, link_global)) {
*link_id = (int)dict->link_global;
mask |= HL_GLOBAL;
} else {
*link_id = (int)dict->link;
+5 -3
View File
@@ -1641,7 +1641,8 @@ static void highlight_list_one(const int id)
static bool hlgroup2dict(Dict *hl, NS ns_id, int hl_id, Arena *arena)
{
HlGroup *sgp = &hl_table[hl_id - 1];
int link = ns_id == 0 ? sgp->sg_link : ns_get_hl(&ns_id, hl_id, true, sgp->sg_set);
NS ns = ns_id;
int link = ns_id == 0 ? sgp->sg_link : ns_get_hl(&ns, hl_id, true, sgp->sg_set);
if (link == -1) {
return false;
}
@@ -1649,8 +1650,9 @@ static bool hlgroup2dict(Dict *hl, NS ns_id, int hl_id, Arena *arena)
// table entry was created but not ever set
return false;
}
HlAttrs attr =
syn_attr2entry(ns_id == 0 ? sgp->sg_attr : ns_get_hl(&ns_id, hl_id, false, sgp->sg_set));
ns = ns_id;
HlAttrs attr = syn_attr2entry(ns_id == 0 ? sgp->sg_attr : ns_get_hl(&ns, hl_id, false,
sgp->sg_set));
*hl = arena_dict(arena, HLATTRS_DICT_SIZE + 1);
if (attr.rgb_ae_attr & HL_DEFAULT) {
PUT_C(*hl, "default", BOOLEAN_OBJ(true));
+16
View File
@@ -610,6 +610,22 @@ describe('API: get highlight', function()
eq(hl, api.nvim_get_hl(0, { name = 'Foo', link = true }))
end)
it('link_global resolves in global namespace', function()
local ns = api.nvim_create_namespace('hl_test')
api.nvim_set_hl(0, 'GlTarget', { fg = '#ff0000' })
api.nvim_set_hl(ns, 'GlSource', { link_global = fn.hlID('GlTarget') })
eq({ link = 'GlTarget' }, api.nvim_get_hl(ns, { name = 'GlSource' }))
-- when both link and link_global are given, link_global wins
api.nvim_set_hl(0, 'OtherTarget', { fg = '#00ff00' })
api.nvim_set_hl(
ns,
'GlSource',
{ link = fn.hlID('OtherTarget'), link_global = fn.hlID('GlTarget') }
)
eq({ link = 'GlTarget' }, api.nvim_get_hl(ns, { name = 'GlSource' }))
end)
it("doesn't contain unset groups", function()
local id = api.nvim_get_hl_id_by_name '@foobar.hubbabubba'
ok(id > 0)