mirror of
https://github.com/neovim/neovim.git
synced 2026-03-27 19:02:02 +00:00
feat(api): nvim_set_hl{update:boolean} #37546
Problem: nvim_set_hl always replaces all attributes. Solution: Add update field. When true, merge with existing attributes instead of replacing. Unspecified attributes are preserved. If highlight group doesn't exist, falls back to reset mode.
This commit is contained in:
@@ -1589,6 +1589,8 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
|
||||
• underdotted: boolean
|
||||
• underdouble: boolean
|
||||
• underline: boolean
|
||||
• update: boolean false by default; true updates only
|
||||
specified attributes, leaving others unchanged.
|
||||
|
||||
nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()*
|
||||
Set active namespace for highlights defined with |nvim_set_hl()|. This can
|
||||
|
||||
@@ -180,6 +180,7 @@ API
|
||||
execute code while nvim is blocking for input.
|
||||
• |vim.secure.trust()| accepts `path` for the `allow` action.
|
||||
• |nvim_get_chan_info()| includes `exitcode` field for terminal buffers
|
||||
• |nvim_set_hl()| supports updating specified attributes only.
|
||||
|
||||
BUILD
|
||||
|
||||
|
||||
1
runtime/lua/vim/_meta/api.lua
generated
1
runtime/lua/vim/_meta/api.lua
generated
@@ -2253,6 +2253,7 @@ function vim.api.nvim_set_decoration_provider(ns_id, opts) end
|
||||
--- - underdotted: boolean
|
||||
--- - underdouble: boolean
|
||||
--- - underline: boolean
|
||||
--- - update: boolean false by default; true updates only specified attributes, leaving 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
runtime/lua/vim/_meta/api_keysets.lua
generated
1
runtime/lua/vim/_meta/api_keysets.lua
generated
@@ -329,6 +329,7 @@ error('Cannot require a meta file')
|
||||
--- @field fg_indexed? boolean
|
||||
--- @field bg_indexed? boolean
|
||||
--- @field force? boolean
|
||||
--- @field update? boolean
|
||||
--- @field url? string
|
||||
|
||||
--- @class vim.api.keyset.highlight_cterm
|
||||
|
||||
@@ -206,6 +206,7 @@ typedef struct {
|
||||
Boolean fg_indexed;
|
||||
Boolean bg_indexed;
|
||||
Boolean force;
|
||||
Boolean update;
|
||||
String url;
|
||||
} Dict(highlight);
|
||||
|
||||
|
||||
@@ -170,9 +170,8 @@ DictAs(get_hl_info) nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena
|
||||
/// - underdotted: boolean
|
||||
/// - underdouble: boolean
|
||||
/// - underline: boolean
|
||||
/// - update: boolean false by default; true updates only specified attributes, leaving others unchanged.
|
||||
/// @param[out] err Error details, if any
|
||||
///
|
||||
// TODO(bfredl): val should take update vs reset flag
|
||||
void nvim_set_hl(uint64_t channel_id, Integer ns_id, String name, Dict(highlight) *val, Error *err)
|
||||
FUNC_API_SINCE(7)
|
||||
{
|
||||
@@ -188,7 +187,14 @@ void nvim_set_hl(uint64_t channel_id, Integer ns_id, String name, Dict(highlight
|
||||
return;
|
||||
}
|
||||
|
||||
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
|
||||
bool update = HAS_KEY(val, highlight, update) && val->update;
|
||||
HlAttrs *base = NULL;
|
||||
HlAttrs base_attrs;
|
||||
if (update && hl_ns_get_attrs((int)ns_id, hl_id, NULL, &base_attrs)) {
|
||||
base = &base_attrs;
|
||||
}
|
||||
|
||||
HlAttrs attrs = dict2hlattrs(val, true, &link_id, base, err);
|
||||
if (!ERROR_SET(err)) {
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
|
||||
|
||||
@@ -221,7 +221,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
|
||||
fallback = false;
|
||||
Dict(highlight) dict = KEYDICT_INIT;
|
||||
if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, ret.data.dict, &err)) {
|
||||
attrs = dict2hlattrs(&dict, true, &it.link_id, &err);
|
||||
attrs = dict2hlattrs(&dict, true, &it.link_id, NULL, &err);
|
||||
fallback = GET_BOOL_OR_TRUE(&dict, highlight, fallback);
|
||||
tmp = dict.fallback; // or false
|
||||
if (it.link_id >= 0) {
|
||||
@@ -291,6 +291,28 @@ bool win_check_ns_hl(win_T *wp)
|
||||
return hl_check_ns();
|
||||
}
|
||||
|
||||
/// Get highlight attributes for a highlight group
|
||||
///
|
||||
/// @param ns_id Namespace ID (0 for global namespace)
|
||||
/// @param hl_id Highlight group ID (1-based)
|
||||
/// @param[in] optional If non-NULL, passed to syn_ns_id2attr to track
|
||||
/// whether the group was explicitly defined in the namespace.
|
||||
/// @param[out] attrs Pointer to store the attributes
|
||||
/// @return true if highlight group exists and has valid attributes
|
||||
bool hl_ns_get_attrs(int ns_id, int hl_id, bool *optional, HlAttrs *attrs)
|
||||
{
|
||||
bool opt = optional ? *optional : true;
|
||||
int syn_attr = syn_ns_id2attr(ns_id, hl_id, &opt);
|
||||
if (optional) {
|
||||
*optional = opt;
|
||||
}
|
||||
if (syn_attr <= 0) {
|
||||
return false;
|
||||
}
|
||||
*attrs = syn_attr2entry(syn_attr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get attribute code for a builtin highlight group.
|
||||
///
|
||||
/// The final syntax group could be modified by hi-link or 'winhighlight'.
|
||||
@@ -300,11 +322,7 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
|
||||
bool available = false;
|
||||
|
||||
if (final_id > 0) {
|
||||
int syn_attr = syn_ns_id2attr(ns_id, final_id, &optional);
|
||||
if (syn_attr > 0) {
|
||||
attrs = syn_attr2entry(syn_attr);
|
||||
available = true;
|
||||
}
|
||||
available = hl_ns_get_attrs(ns_id, final_id, &optional, &attrs);
|
||||
}
|
||||
|
||||
if (HLF_PNI <= idx && idx <= HLF_PST) {
|
||||
@@ -1005,49 +1023,53 @@ void hlattrs2dict(Dict *hl, Dict *hl_attrs, HlAttrs ae, bool use_rgb, bool short
|
||||
}
|
||||
}
|
||||
|
||||
HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
|
||||
HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, HlAttrs *base, Error *err)
|
||||
{
|
||||
#define HAS_KEY_X(d, key) HAS_KEY(d, highlight, key)
|
||||
HlAttrs hlattrs = HLATTRS_INIT;
|
||||
int32_t fg = -1;
|
||||
int32_t bg = -1;
|
||||
int32_t ctermfg = -1;
|
||||
int32_t ctermbg = -1;
|
||||
int32_t sp = -1;
|
||||
int blend = -1;
|
||||
int32_t mask = 0;
|
||||
int32_t cterm_mask = 0;
|
||||
int32_t fg = base ? base->rgb_fg_color : -1;
|
||||
int32_t bg = base ? base->rgb_bg_color : -1;
|
||||
int32_t ctermfg = base ? (base->cterm_fg_color == 0 ? -1 : base->cterm_fg_color - 1) : -1;
|
||||
int32_t ctermbg = base ? (base->cterm_bg_color == 0 ? -1 : base->cterm_bg_color - 1) : -1;
|
||||
int32_t sp = base ? base->rgb_sp_color : -1;
|
||||
int blend = base ? base->hl_blend : -1;
|
||||
int32_t mask = base ? base->rgb_ae_attr : 0;
|
||||
int32_t cterm_mask = base ? base->cterm_ae_attr : 0;
|
||||
bool cterm_mask_provided = false;
|
||||
|
||||
#define CHECK_FLAG(d, m, name, extra, flag) \
|
||||
if (d->name##extra) { \
|
||||
if (flag & HL_UNDERLINE_MASK) { \
|
||||
m &= ~HL_UNDERLINE_MASK; \
|
||||
#define CHECK_FLAG_WITH_KEY(d, m, name, extra, flag) \
|
||||
if (HAS_KEY_X(d, name)) { \
|
||||
if (d->name##extra) { \
|
||||
if (flag & HL_UNDERLINE_MASK) { \
|
||||
m &= ~HL_UNDERLINE_MASK; \
|
||||
} \
|
||||
m |= flag; \
|
||||
} else { \
|
||||
m &= ~flag; \
|
||||
} \
|
||||
m |= flag; \
|
||||
}
|
||||
|
||||
CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
|
||||
CHECK_FLAG(dict, mask, bold, , HL_BOLD);
|
||||
CHECK_FLAG(dict, mask, italic, , HL_ITALIC);
|
||||
CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE);
|
||||
CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL);
|
||||
CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE);
|
||||
CHECK_FLAG(dict, mask, underdotted, , HL_UNDERDOTTED);
|
||||
CHECK_FLAG(dict, mask, underdashed, , HL_UNDERDASHED);
|
||||
CHECK_FLAG(dict, mask, standout, , HL_STANDOUT);
|
||||
CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH);
|
||||
CHECK_FLAG(dict, mask, altfont, , HL_ALTFONT);
|
||||
CHECK_FLAG(dict, mask, dim, , HL_DIM);
|
||||
CHECK_FLAG(dict, mask, blink, , HL_BLINK);
|
||||
CHECK_FLAG(dict, mask, conceal, , HL_CONCEALED);
|
||||
CHECK_FLAG(dict, mask, overline, , HL_OVERLINE);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, reverse, , HL_INVERSE);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, bold, , HL_BOLD);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, italic, , HL_ITALIC);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, underline, , HL_UNDERLINE);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, undercurl, , HL_UNDERCURL);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, underdouble, , HL_UNDERDOUBLE);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, underdotted, , HL_UNDERDOTTED);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, underdashed, , HL_UNDERDASHED);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, standout, , HL_STANDOUT);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, strikethrough, , HL_STRIKETHROUGH);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, altfont, , HL_ALTFONT);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, dim, , HL_DIM);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, blink, , HL_BLINK);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, conceal, , HL_CONCEALED);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, overline, , HL_OVERLINE);
|
||||
if (use_rgb) {
|
||||
CHECK_FLAG(dict, mask, fg_indexed, , HL_FG_INDEXED);
|
||||
CHECK_FLAG(dict, mask, bg_indexed, , HL_BG_INDEXED);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, fg_indexed, , HL_FG_INDEXED);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, bg_indexed, , HL_BG_INDEXED);
|
||||
}
|
||||
CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
|
||||
CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, nocombine, , HL_NOCOMBINE);
|
||||
CHECK_FLAG_WITH_KEY(dict, mask, default, _, HL_DEFAULT);
|
||||
|
||||
if (HAS_KEY_X(dict, fg)) {
|
||||
fg = object_to_color(dict->fg, "fg", use_rgb, err);
|
||||
@@ -1111,6 +1133,16 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
|
||||
}
|
||||
|
||||
cterm_mask_provided = true;
|
||||
cterm_mask = 0;
|
||||
|
||||
#define CHECK_FLAG(d, m, name, extra, flag) \
|
||||
if (d->name##extra) { \
|
||||
if (flag & HL_UNDERLINE_MASK) { \
|
||||
m &= ~HL_UNDERLINE_MASK; \
|
||||
} \
|
||||
m |= flag; \
|
||||
}
|
||||
|
||||
CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE);
|
||||
CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD);
|
||||
CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC);
|
||||
@@ -1129,6 +1161,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
|
||||
CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE);
|
||||
}
|
||||
#undef CHECK_FLAG
|
||||
#undef CHECK_FLAG_WITH_KEY
|
||||
|
||||
if (HAS_KEY_X(dict, ctermfg)) {
|
||||
ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err);
|
||||
|
||||
@@ -936,6 +936,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
|
||||
g->sg_link = 0;
|
||||
}
|
||||
|
||||
bool update = HAS_KEY(dict, highlight, update) && dict->update;
|
||||
g->sg_gui = attrs.rgb_ae_attr &~HL_DEFAULT;
|
||||
|
||||
g->sg_rgb_fg = attrs.rgb_fg_color;
|
||||
@@ -954,20 +955,29 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
|
||||
};
|
||||
|
||||
for (int j = 0; cattrs[j].dest; j++) {
|
||||
if (cattrs[j].val < 0) {
|
||||
if (cattrs[j].name.type != kObjectTypeNil) {
|
||||
if (cattrs[j].val < 0) {
|
||||
*cattrs[j].dest = kColorIdxNone;
|
||||
} else if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
|
||||
name_to_color(cattrs[j].name.data.string.data, cattrs[j].dest);
|
||||
} else {
|
||||
*cattrs[j].dest = kColorIdxHex;
|
||||
}
|
||||
} else if (!update) {
|
||||
*cattrs[j].dest = kColorIdxNone;
|
||||
} else if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
|
||||
name_to_color(cattrs[j].name.data.string.data, cattrs[j].dest);
|
||||
} else {
|
||||
*cattrs[j].dest = kColorIdxHex;
|
||||
}
|
||||
}
|
||||
|
||||
g->sg_cterm = attrs.cterm_ae_attr &~HL_DEFAULT;
|
||||
g->sg_cterm_bg = attrs.cterm_bg_color;
|
||||
g->sg_cterm_fg = attrs.cterm_fg_color;
|
||||
|
||||
g->sg_cterm_bold = g->sg_cterm & HL_BOLD;
|
||||
g->sg_blend = attrs.hl_blend;
|
||||
if (attrs.hl_blend != -1) {
|
||||
g->sg_blend = attrs.hl_blend;
|
||||
} else if (!update) {
|
||||
g->sg_blend = -1;
|
||||
}
|
||||
|
||||
g->sg_script_ctx = current_sctx;
|
||||
g->sg_script_ctx.sc_lnum += SOURCING_LNUM;
|
||||
|
||||
@@ -223,7 +223,7 @@ static HlAttrs ui_client_dict2hlattrs(Dict d, bool rgb)
|
||||
return HLATTRS_INIT;
|
||||
}
|
||||
|
||||
HlAttrs attrs = dict2hlattrs(&dict, rgb, NULL, &err);
|
||||
HlAttrs attrs = dict2hlattrs(&dict, rgb, NULL, NULL, &err);
|
||||
|
||||
if (HAS_KEY(&dict, highlight, url)) {
|
||||
attrs.url = tui_add_url(tui, dict.url.data);
|
||||
|
||||
@@ -257,6 +257,40 @@ describe('API: set highlight', function()
|
||||
)
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('update=true sets only specified keys', function()
|
||||
api.nvim_set_hl(0, 'TestGroup', { fg = '#ff0000', bg = '#0000ff', bold = true })
|
||||
api.nvim_set_hl(0, 'TestGroup', { bg = '#00ff00', update = true })
|
||||
local hl = api.nvim_get_hl(0, { name = 'TestGroup' })
|
||||
eq(16711680, hl.fg)
|
||||
eq(65280, hl.bg)
|
||||
eq(true, hl.bold)
|
||||
|
||||
api.nvim_set_hl(0, 'TestGroup', { bold = false, update = true })
|
||||
hl = api.nvim_get_hl(0, { name = 'TestGroup' })
|
||||
eq(nil, hl.bold)
|
||||
eq(16711680, hl.fg)
|
||||
|
||||
api.nvim_set_hl(0, 'TestGroup', { italic = true })
|
||||
|
||||
hl = api.nvim_get_hl(0, { name = 'TestGroup' })
|
||||
eq(nil, hl.fg)
|
||||
eq(nil, hl.bg)
|
||||
eq(true, hl.italic)
|
||||
|
||||
local ns = api.nvim_create_namespace('test')
|
||||
api.nvim_set_hl(ns, 'TestGroup', { fg = '#ff0000', italic = true })
|
||||
api.nvim_set_hl(ns, 'TestGroup', { fg = '#00ff00', update = true })
|
||||
hl = api.nvim_get_hl(ns, { name = 'TestGroup' })
|
||||
eq(65280, hl.fg)
|
||||
eq(true, hl.italic)
|
||||
|
||||
api.nvim_set_hl(0, 'LinkedGroup', { link = 'Normal' })
|
||||
api.nvim_set_hl(0, 'LinkedGroup', { bold = true, update = true })
|
||||
hl = api.nvim_get_hl(0, { name = 'LinkedGroup' })
|
||||
eq(nil, hl.link)
|
||||
eq(true, hl.bold)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('API: get highlight', function()
|
||||
|
||||
Reference in New Issue
Block a user