mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 09:31:47 +00:00
feat(treesitter/extmark): support removing a conceal highlight #35087
Problem: Cannot remove a `@conceal` highlight when defined in highlights.scm. Solution: Support a `@noconceal` highlight that works similarly to `@nospell` where it overrides the conceal set on the range to remove it. Additionally, can set the conceal metadata field to false for the same behavior.
This commit is contained in:
@@ -3324,11 +3324,14 @@ nvim_buf_set_extmark({buf}, {ns_id}, {line}, {col}, {opts})
|
||||
• cursorline_hl_group: highlight group used for the sign
|
||||
column text when the cursor is on the same line as the mark
|
||||
and 'cursorline' is enabled.
|
||||
• conceal: string which should be either empty or a single
|
||||
character. Enable concealing similar to |:syn-conceal|.
|
||||
When a character is supplied it is used as |:syn-cchar|.
|
||||
"hl_group" is used as highlight for the cchar if provided,
|
||||
otherwise it defaults to |hl-Conceal|.
|
||||
• conceal: either a boolean or a string.
|
||||
• when a string is given, it should be either empty or a
|
||||
single character. Enable concealing similar to
|
||||
|:syn-conceal|. When a character is supplied it is used
|
||||
as |:syn-cchar|. "hl_group" is used as highlight for the
|
||||
cchar if provided, otherwise it defaults to |hl-Conceal|.
|
||||
• when a boolean is given, true is equivalent to "" and
|
||||
false overrides any existing conceal to remove it.
|
||||
• conceal_lines: string which should be empty. When provided,
|
||||
lines in the range are not drawn at all (according to
|
||||
'conceallevel'); the next unconcealed line is drawn
|
||||
|
||||
@@ -197,6 +197,8 @@ TERMINAL
|
||||
TREESITTER
|
||||
|
||||
• |v_]N| |v_[N| expand selection to sibling treesitter node.
|
||||
• |treesitter-highlight-conceal| can be removed by adding a `@noconceal`
|
||||
capture.
|
||||
|
||||
TUI
|
||||
|
||||
|
||||
@@ -522,6 +522,11 @@ character will be used: >query
|
||||
; identifiers will be concealed with 'f'.
|
||||
((identifier) @conceal (#set! conceal "foo"))
|
||||
<
|
||||
To remove a conceal highlight, you can use the `@noconceal` capture to
|
||||
override any existing conceal highlight set for the range. >query
|
||||
|
||||
((identifier) @noconceal)
|
||||
<
|
||||
|
||||
*treesitter-highlight-priority*
|
||||
Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default
|
||||
|
||||
13
runtime/lua/vim/_meta/api.gen.lua
generated
13
runtime/lua/vim/_meta/api.gen.lua
generated
@@ -702,11 +702,14 @@ function vim.api.nvim_buf_line_count(buf) end
|
||||
--- - cursorline_hl_group: highlight group used for the sign
|
||||
--- column text when the cursor is on the same line as the
|
||||
--- mark and 'cursorline' is enabled.
|
||||
--- - conceal: string which should be either empty or a single
|
||||
--- character. Enable concealing similar to `:syn-conceal`.
|
||||
--- When a character is supplied it is used as `:syn-cchar`.
|
||||
--- "hl_group" is used as highlight for the cchar if provided,
|
||||
--- otherwise it defaults to `hl-Conceal`.
|
||||
--- - conceal: either a boolean or a string.
|
||||
--- - when a string is given, it should be either empty or a single
|
||||
--- character. Enable concealing similar to `:syn-conceal`.
|
||||
--- When a character is supplied it is used as `:syn-cchar`.
|
||||
--- "hl_group" is used as highlight for the cchar if provided,
|
||||
--- otherwise it defaults to `hl-Conceal`.
|
||||
--- - when a boolean is given, true is equivalent to "" and false
|
||||
--- overrides any existing conceal to remove it.
|
||||
--- - conceal_lines: string which should be empty. When
|
||||
--- provided, lines in the range are not drawn at all
|
||||
--- (according to 'conceallevel'); the next unconcealed line
|
||||
|
||||
2
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
2
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
@@ -437,7 +437,7 @@ error('Cannot require a meta file')
|
||||
--- @field number_hl_group? integer|string
|
||||
--- @field line_hl_group? integer|string
|
||||
--- @field cursorline_hl_group? integer|string
|
||||
--- @field conceal? string
|
||||
--- @field conceal? string|boolean
|
||||
--- @field conceal_lines? string
|
||||
--- @field spell? boolean
|
||||
--- @field ui_watched? boolean
|
||||
|
||||
@@ -22,7 +22,7 @@ error('Cannot require a meta file')
|
||||
--- @field hl_group? string
|
||||
--- @field hl_eol? boolean
|
||||
---
|
||||
--- @field conceal? string
|
||||
--- @field conceal? string|false
|
||||
--- @field spell? boolean
|
||||
--- @field ui_watched? boolean
|
||||
--- @field url? string
|
||||
|
||||
@@ -427,14 +427,29 @@ local function on_range_impl(
|
||||
|
||||
local spell, spell_pri_offset = get_spell(capture_name)
|
||||
|
||||
local is_noconceal = capture_name == 'noconceal'
|
||||
-- The "conceal" attribute can be set at the pattern level or on a particular capture
|
||||
local conceal_attr = (metadata.conceal ~= nil and metadata.conceal)
|
||||
or (metadata[capture] and metadata[capture].conceal)
|
||||
local conceal ---@type boolean|string?
|
||||
if is_noconceal then
|
||||
conceal = false
|
||||
else
|
||||
conceal = conceal_attr
|
||||
if conceal_attr == false then
|
||||
is_noconceal = true
|
||||
end
|
||||
end
|
||||
is_noconceal = is_noconceal or conceal_attr == false
|
||||
local conceal_pri_offset = is_noconceal and 1 or 0
|
||||
|
||||
-- The "priority" attribute can be set at the pattern level or on a particular capture
|
||||
local priority = (
|
||||
vim._tointeger(metadata.priority or metadata[capture] and metadata[capture].priority)
|
||||
or vim.hl.priorities.treesitter
|
||||
) + spell_pri_offset
|
||||
|
||||
-- The "conceal" attribute can be set at the pattern level or on a particular capture
|
||||
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
|
||||
)
|
||||
+ spell_pri_offset
|
||||
+ conceal_pri_offset
|
||||
|
||||
local url = get_url(match, buf, capture, metadata)
|
||||
|
||||
|
||||
@@ -609,7 +609,7 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?']
|
||||
---@class vim.treesitter.query.TSMetadata
|
||||
---@field range? Range
|
||||
---@field offset? Range4
|
||||
---@field conceal? string
|
||||
---@field conceal? string|boolean
|
||||
---@field bo.commentstring? string
|
||||
---@field [integer]? vim.treesitter.query.TSMetadata
|
||||
---@field [string]? integer|string
|
||||
|
||||
@@ -480,7 +480,8 @@ local function styletable_treesitter(state)
|
||||
if c ~= nil then
|
||||
local hlid = register_hl(state, '@' .. c .. '.' .. tree:lang())
|
||||
if metadata.conceal and state.opt.conceallevel ~= 0 then
|
||||
styletable_insert_conceal(state, srow + 1, scol + 1, erow + 1, ecol + 1, metadata.conceal)
|
||||
local conceal = metadata.conceal == true and '' or metadata.conceal --[[@as string]]
|
||||
styletable_insert_conceal(state, srow + 1, scol + 1, erow + 1, ecol + 1, conceal)
|
||||
end
|
||||
styletable_insert_range(state, srow + 1, scol + 1, erow + 1, ecol + 1, hlid)
|
||||
end
|
||||
|
||||
@@ -507,11 +507,14 @@ ArrayOf(DictAs(get_extmark_item)) nvim_buf_get_extmarks(Buffer buf, Integer ns_i
|
||||
/// - cursorline_hl_group: highlight group used for the sign
|
||||
/// column text when the cursor is on the same line as the
|
||||
/// mark and 'cursorline' is enabled.
|
||||
/// - conceal: string which should be either empty or a single
|
||||
/// character. Enable concealing similar to |:syn-conceal|.
|
||||
/// When a character is supplied it is used as |:syn-cchar|.
|
||||
/// "hl_group" is used as highlight for the cchar if provided,
|
||||
/// otherwise it defaults to |hl-Conceal|.
|
||||
/// - conceal: either a boolean or a string.
|
||||
/// - when a string is given, it should be either empty or a single
|
||||
/// character. Enable concealing similar to |:syn-conceal|.
|
||||
/// When a character is supplied it is used as |:syn-cchar|.
|
||||
/// "hl_group" is used as highlight for the cchar if provided,
|
||||
/// otherwise it defaults to |hl-Conceal|.
|
||||
/// - when a boolean is given, true is equivalent to "" and false
|
||||
/// overrides any existing conceal to remove it.
|
||||
/// - conceal_lines: string which should be empty. When
|
||||
/// provided, lines in the range are not drawn at all
|
||||
/// (according to 'conceallevel'); the next unconcealed line
|
||||
@@ -633,14 +636,25 @@ Integer nvim_buf_set_extmark(Buffer buf, Integer ns_id, Integer line, Integer co
|
||||
}
|
||||
|
||||
if (HAS_KEY(opts, set_extmark, conceal)) {
|
||||
hl.flags |= kSHConceal;
|
||||
VALIDATE_EXP((opts->conceal.type == kObjectTypeBoolean
|
||||
|| opts->conceal.type == kObjectTypeString),
|
||||
"conceal", "String or Boolean", api_typename(opts->conceal.type), {
|
||||
goto error;
|
||||
});
|
||||
|
||||
has_hl = true;
|
||||
if (opts->conceal.size > 0) {
|
||||
int ch;
|
||||
hl.conceal_char = utfc_ptr2schar(opts->conceal.data, &ch);
|
||||
VALIDATE(hl.conceal_char && vim_isprintc(ch), "%s", "conceal char has to be printable", {
|
||||
goto error;
|
||||
});
|
||||
if (opts->conceal.type == kObjectTypeBoolean) {
|
||||
hl.flags |= opts->conceal.data.boolean ? kSHConceal : kSHConcealOff;
|
||||
} else {
|
||||
String conceal_str = opts->conceal.data.string;
|
||||
hl.flags |= kSHConceal;
|
||||
if (conceal_str.size > 0) {
|
||||
int ch;
|
||||
hl.conceal_char = utfc_ptr2schar(conceal_str.data, &ch);
|
||||
VALIDATE(hl.conceal_char && vim_isprintc(ch), "%s", "conceal char must be printable", {
|
||||
goto error;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ typedef struct {
|
||||
HLGroupID number_hl_group;
|
||||
HLGroupID line_hl_group;
|
||||
HLGroupID cursorline_hl_group;
|
||||
String conceal;
|
||||
Union(String, Boolean) conceal;
|
||||
String conceal_lines;
|
||||
Boolean spell;
|
||||
Boolean ui_watched;
|
||||
|
||||
@@ -119,7 +119,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, int col1, DecorInline decor)
|
||||
void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh)
|
||||
{
|
||||
if (sh.hl_id || (sh.url != NULL)
|
||||
|| (sh.flags & (kSHIsSign | kSHSpellOn | kSHSpellOff | kSHConceal))) {
|
||||
|| (sh.flags & (kSHIsSign | kSHSpellOn | kSHSpellOff | kSHConceal | kSHConcealOff))) {
|
||||
if (row2 >= row1) {
|
||||
redraw_buf_range_later(buf, row1 + 1, row2 + 1);
|
||||
}
|
||||
@@ -644,7 +644,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end
|
||||
};
|
||||
|
||||
if (sh->hl_id || (sh->url != NULL)
|
||||
|| (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) {
|
||||
|| (sh->flags & (kSHConcealOff | kSHConceal | kSHSpellOn | kSHSpellOff))) {
|
||||
if (sh->hl_id) {
|
||||
range.attr_id = syn_id2attr(sh->hl_id);
|
||||
}
|
||||
@@ -800,6 +800,10 @@ next_mark:
|
||||
}
|
||||
}
|
||||
|
||||
if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHConcealOff)) {
|
||||
conceal = 0;
|
||||
}
|
||||
|
||||
if (r->kind == kDecorKindHighlight) {
|
||||
if (r->data.sh.flags & kSHSpellOn) {
|
||||
spell = kTrue;
|
||||
@@ -1213,7 +1217,9 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar
|
||||
priority = sh_hl.priority;
|
||||
}
|
||||
|
||||
if (sh_hl.flags & kSHConceal) {
|
||||
if (sh_hl.flags & kSHConcealOff) {
|
||||
PUT_C(*dict, "conceal", BOOLEAN_OBJ(false));
|
||||
} else if (sh_hl.flags & kSHConceal) {
|
||||
char buf[MAX_SCHAR_SIZE];
|
||||
schar_get(buf, sh_hl.text[0]);
|
||||
PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf));
|
||||
|
||||
@@ -56,6 +56,7 @@ enum {
|
||||
kSHSpellOff = 32,
|
||||
kSHConceal = 64,
|
||||
kSHConcealLines = 128,
|
||||
kSHConcealOff = 256,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -864,6 +864,37 @@ describe('treesitter highlighting (C)', function()
|
||||
})
|
||||
end)
|
||||
|
||||
it('supports @noconceal', function()
|
||||
insert([[
|
||||
int foo = bar;
|
||||
]])
|
||||
|
||||
exec_lua(function()
|
||||
vim.opt.cole = 2
|
||||
local parser = vim.treesitter.get_parser(0, 'c')
|
||||
vim.treesitter.highlighter.new(parser, {
|
||||
queries = {
|
||||
c = [[
|
||||
((identifier) @conceal
|
||||
(#set! conceal "X"))
|
||||
|
||||
((identifier) @noconceal
|
||||
(#eq? @noconceal "bar"))
|
||||
]],
|
||||
},
|
||||
})
|
||||
end)
|
||||
|
||||
screen:expect({
|
||||
grid = [[
|
||||
int {14:X} = bar; |
|
||||
^ |
|
||||
{1:~ }|*15
|
||||
|
|
||||
]],
|
||||
})
|
||||
end)
|
||||
|
||||
it('@foo.bar groups has the correct fallback behavior', function()
|
||||
local get_hl = function(name)
|
||||
return api.nvim_get_hl_by_name(name, 1).foreground
|
||||
|
||||
@@ -2175,7 +2175,7 @@ describe('extmark decorations', function()
|
||||
command('set conceallevel=1')
|
||||
screen:expect_unchanged()
|
||||
|
||||
eq('conceal char has to be printable', pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_col = 0, end_row = 2, conceal = '\255' }))
|
||||
eq('conceal char must be printable', pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_col = 0, end_row = 2, conceal = '\255' }))
|
||||
end)
|
||||
|
||||
it('conceal with composed conceal char', function()
|
||||
|
||||
Reference in New Issue
Block a user