From 9383a096eb7eef0693505508a0414dd3ff2221eb Mon Sep 17 00:00:00 2001 From: glepnir Date: Sat, 28 Mar 2026 15:26:46 +0800 Subject: [PATCH] fix(api): nvim_set_hl boolean false corrupts underline styles (#38504) Problem: setting one underline style to false clears bits belonging to another style. `{underdouble = true, underdashed = false}` results in undercurl because CHECK_FLAG_WITH_KEY does `m &= ~flag` which doesn't work for multi-bit encoded values sharing HL_UNDERLINE_MASK. Solution: use a local variable to derive the correct clear mask from the flag. Clear the whole HL_UNDERLINE_MASK field instead of individual bits, and only clear on false when the current style actually matches. --- src/nvim/highlight.c | 11 ++++----- test/functional/api/highlight_spec.lua | 33 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 2211dc39d0..37972c8e33 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -1039,13 +1039,12 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, HlAttrs #define CHECK_FLAG_WITH_KEY(d, m, name, extra, flag) \ if (HAS_KEY_X(d, name)) { \ + int32_t flag_ = (flag); \ + int32_t cmask_ = (flag_ & HL_UNDERLINE_MASK) ? HL_UNDERLINE_MASK : flag_; \ if (d->name##extra) { \ - if (flag & HL_UNDERLINE_MASK) { \ - m &= ~HL_UNDERLINE_MASK; \ - } \ - m |= flag; \ - } else { \ - m &= ~flag; \ + m = (m & ~cmask_) | flag_; \ + } else if ((m & cmask_) == flag_) { \ + m &= ~cmask_; \ } \ } diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 2da2df0bd0..1707a0728b 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -290,6 +290,39 @@ describe('API: set highlight', function() hl = api.nvim_get_hl(0, { name = 'LinkedGroup' }) eq(nil, hl.link) eq(true, hl.bold) + + -- underline style flags: false must not corrupt other styles + local unders = { 'underline', 'undercurl', 'underdouble', 'underdotted', 'underdashed' } + for _, a in ipairs(unders) do + for _, b in ipairs(unders) do + if a ~= b then + api.nvim_set_hl(0, 'TestGroup', { [a] = true, [b] = false }) + hl = api.nvim_get_hl(0, { name = 'TestGroup' }) + eq(true, hl[a]) + eq(nil, hl[b]) + end + end + end + api.nvim_set_hl(0, 'TestGroup', { underdouble = true, fg = '#ff0000', bold = true }) + api.nvim_set_hl(0, 'TestGroup', { fg = '#00ff00', update = true }) + hl = api.nvim_get_hl(0, { name = 'TestGroup' }) + eq(true, hl.underdouble) + eq(true, hl.bold) + eq(65280, hl.fg) + + api.nvim_set_hl(0, 'TestGroup', { underdashed = true, update = true }) + hl = api.nvim_get_hl(0, { name = 'TestGroup' }) + eq(true, hl.underdashed) + eq(nil, hl.underdouble) + + api.nvim_set_hl(0, 'TestGroup', { underdouble = true, bold = true }) + api.nvim_set_hl(0, 'TestGroup', { underdashed = false, update = true }) + hl = api.nvim_get_hl(0, { name = 'TestGroup' }) + eq(true, hl.underdouble) + api.nvim_set_hl(0, 'TestGroup', { underdouble = false, update = true }) + hl = api.nvim_get_hl(0, { name = 'TestGroup' }) + eq(nil, hl.underdouble) + eq(true, hl.bold) end) end)