mirror of
https://github.com/neovim/neovim.git
synced 2025-10-05 09:26:30 +00:00
Merge pull request #19931 from bfredl/scopedhl
feat(highlight)!: use scoped @foo.bar.special groups for tree-sitter highlight
This commit is contained in:
@@ -323,16 +323,34 @@ for a buffer with this code: >
|
||||
local query2 = [[ ... ]]
|
||||
highlighter:set_query(query2)
|
||||
|
||||
As mentioned above the supported predicate is currently only `eq?`. `match?`
|
||||
predicates behave like matching always fails. As an addition a capture which
|
||||
begin with an upper-case letter like `@WarningMsg` will map directly to this
|
||||
highlight group, if defined. Also if the predicate begins with upper-case and
|
||||
contains a dot only the part before the first will be interpreted as the
|
||||
highlight group. As an example, this warns of a binary expression with two
|
||||
|
||||
*lua-treesitter-highlight-groups*
|
||||
The capture names, with `@` included, are directly usable as highlight groups.
|
||||
A fallback system is implemented, so that more specific groups fallback to
|
||||
more generic ones. For instance, in a language that has separate doc
|
||||
comments, `@comment.doc` could be used. If this group is not defined, the
|
||||
highlighting for an ordinary `@comment` is used. This way, existing color
|
||||
schemes already work out of the box, but it is possible to add
|
||||
more specific variants for queries that make them available.
|
||||
|
||||
As an additional rule, captures highlights can always be specialized by
|
||||
language, by appending the language name after an additional dot. For
|
||||
instance, to highlight comments differently per language: >
|
||||
|
||||
hi @comment.c guifg=Blue
|
||||
hi @comment.lua @guifg=DarkBlue
|
||||
hi link @comment.doc.java String
|
||||
<
|
||||
It is possible to use custom highlight groups. As an example, if we
|
||||
define the `@warning` group: >
|
||||
|
||||
hi link @warning WarningMsg
|
||||
<
|
||||
the following query warns of a binary expression with two
|
||||
identical identifiers, highlighting both as |hl-WarningMsg|: >
|
||||
|
||||
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
|
||||
(eq? @WarningMsg.left @WarningMsg.right))
|
||||
((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right)
|
||||
(eq? @warning.left @warning.right))
|
||||
<
|
||||
Treesitter Highlighting Priority *lua-treesitter-highlight-priority*
|
||||
|
||||
@@ -352,6 +370,18 @@ attribute: >
|
||||
==============================================================================
|
||||
Lua module: vim.treesitter *lua-treesitter-core*
|
||||
|
||||
*get_captures_at_position()*
|
||||
get_captures_at_position({bufnr}, {row}, {col})
|
||||
Gets a list of captures for a given cursor position
|
||||
|
||||
Parameters: ~
|
||||
{bufnr} (number) The buffer number
|
||||
{row} (number) The position row
|
||||
{col} (number) The position column
|
||||
|
||||
Return: ~
|
||||
(table) A table of captures
|
||||
|
||||
get_node_range({node_or_range}) *get_node_range()*
|
||||
Get the node's range or unpack a range table
|
||||
|
||||
@@ -393,6 +423,14 @@ is_ancestor({dest}, {source}) *is_ancestor()*
|
||||
Return: ~
|
||||
(boolean) True if dest is an ancestor of source
|
||||
|
||||
is_in_node_range({node}, {line}, {col}) *is_in_node_range()*
|
||||
Determines whether (line, col) position is in node range
|
||||
|
||||
Parameters: ~
|
||||
{node} Node defining the range
|
||||
{line} A line (0-based)
|
||||
{col} A column (0-based)
|
||||
|
||||
node_contains({node}, {range}) *node_contains()*
|
||||
Determines if a node contains a range
|
||||
|
||||
|
@@ -154,6 +154,28 @@ function M.get_node_range(node_or_range)
|
||||
end
|
||||
end
|
||||
|
||||
---Determines whether (line, col) position is in node range
|
||||
---
|
||||
---@param node Node defining the range
|
||||
---@param line A line (0-based)
|
||||
---@param col A column (0-based)
|
||||
function M.is_in_node_range(node, line, col)
|
||||
local start_line, start_col, end_line, end_col = M.get_node_range(node)
|
||||
if line >= start_line and line <= end_line then
|
||||
if line == start_line and line == end_line then
|
||||
return col >= start_col and col < end_col
|
||||
elseif line == start_line then
|
||||
return col >= start_col
|
||||
elseif line == end_line then
|
||||
return col < end_col
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
---Determines if a node contains a range
|
||||
---@param node table The node
|
||||
---@param range table The range
|
||||
@@ -167,4 +189,56 @@ function M.node_contains(node, range)
|
||||
return start_fits and end_fits
|
||||
end
|
||||
|
||||
---Gets a list of captures for a given cursor position
|
||||
---@param bufnr number The buffer number
|
||||
---@param row number The position row
|
||||
---@param col number The position column
|
||||
---
|
||||
---@returns (table) A table of captures
|
||||
function M.get_captures_at_position(bufnr, row, col)
|
||||
if bufnr == 0 then
|
||||
bufnr = a.nvim_get_current_buf()
|
||||
end
|
||||
local buf_highlighter = M.highlighter.active[bufnr]
|
||||
|
||||
if not buf_highlighter then
|
||||
return {}
|
||||
end
|
||||
|
||||
local matches = {}
|
||||
|
||||
buf_highlighter.tree:for_each_tree(function(tstree, tree)
|
||||
if not tstree then
|
||||
return
|
||||
end
|
||||
|
||||
local root = tstree:root()
|
||||
local root_start_row, _, root_end_row, _ = root:range()
|
||||
|
||||
-- Only worry about trees within the line range
|
||||
if root_start_row > row or root_end_row < row then
|
||||
return
|
||||
end
|
||||
|
||||
local q = buf_highlighter:get_query(tree:lang())
|
||||
|
||||
-- Some injected languages may not have highlight queries.
|
||||
if not q:query() then
|
||||
return
|
||||
end
|
||||
|
||||
local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1)
|
||||
|
||||
for capture, node, metadata in iter do
|
||||
if M.is_in_node_range(node, row, col) then
|
||||
local c = q._query.captures[capture] -- name of the capture in the query
|
||||
if c ~= nil then
|
||||
table.insert(matches, { capture = c, priority = metadata.priority })
|
||||
end
|
||||
end
|
||||
end
|
||||
end, true)
|
||||
return matches
|
||||
end
|
||||
|
||||
return M
|
||||
|
@@ -12,105 +12,18 @@ TSHighlighterQuery.__index = TSHighlighterQuery
|
||||
|
||||
local ns = a.nvim_create_namespace('treesitter/highlighter')
|
||||
|
||||
local _default_highlights = {}
|
||||
local _link_default_highlight_once = function(from, to)
|
||||
if not _default_highlights[from] then
|
||||
_default_highlights[from] = true
|
||||
a.nvim_set_hl(0, from, { link = to, default = true })
|
||||
end
|
||||
|
||||
return from
|
||||
end
|
||||
|
||||
-- If @definition.special does not exist use @definition instead
|
||||
local subcapture_fallback = {
|
||||
__index = function(self, capture)
|
||||
local rtn
|
||||
local shortened = capture
|
||||
while not rtn and shortened do
|
||||
shortened = shortened:match('(.*)%.')
|
||||
rtn = shortened and rawget(self, shortened)
|
||||
end
|
||||
rawset(self, capture, rtn or '__notfound')
|
||||
return rtn
|
||||
end,
|
||||
}
|
||||
|
||||
TSHighlighter.hl_map = setmetatable({
|
||||
['error'] = 'Error',
|
||||
['text.underline'] = 'Underlined',
|
||||
['todo'] = 'Todo',
|
||||
['debug'] = 'Debug',
|
||||
|
||||
-- Miscs
|
||||
['comment'] = 'Comment',
|
||||
['punctuation.delimiter'] = 'Delimiter',
|
||||
['punctuation.bracket'] = 'Delimiter',
|
||||
['punctuation.special'] = 'Delimiter',
|
||||
|
||||
-- Constants
|
||||
['constant'] = 'Constant',
|
||||
['constant.builtin'] = 'Special',
|
||||
['constant.macro'] = 'Define',
|
||||
['define'] = 'Define',
|
||||
['macro'] = 'Macro',
|
||||
['string'] = 'String',
|
||||
['string.regex'] = 'String',
|
||||
['string.escape'] = 'SpecialChar',
|
||||
['character'] = 'Character',
|
||||
['character.special'] = 'SpecialChar',
|
||||
['number'] = 'Number',
|
||||
['boolean'] = 'Boolean',
|
||||
['float'] = 'Float',
|
||||
|
||||
-- Functions
|
||||
['function'] = 'Function',
|
||||
['function.special'] = 'Function',
|
||||
['function.builtin'] = 'Special',
|
||||
['function.macro'] = 'Macro',
|
||||
['parameter'] = 'Identifier',
|
||||
['method'] = 'Function',
|
||||
['field'] = 'Identifier',
|
||||
['property'] = 'Identifier',
|
||||
['constructor'] = 'Special',
|
||||
|
||||
-- Keywords
|
||||
['conditional'] = 'Conditional',
|
||||
['repeat'] = 'Repeat',
|
||||
['label'] = 'Label',
|
||||
['operator'] = 'Operator',
|
||||
['keyword'] = 'Keyword',
|
||||
['exception'] = 'Exception',
|
||||
|
||||
['type'] = 'Type',
|
||||
['type.builtin'] = 'Type',
|
||||
['type.qualifier'] = 'Type',
|
||||
['type.definition'] = 'Typedef',
|
||||
['storageclass'] = 'StorageClass',
|
||||
['structure'] = 'Structure',
|
||||
['include'] = 'Include',
|
||||
['preproc'] = 'PreProc',
|
||||
}, subcapture_fallback)
|
||||
|
||||
---@private
|
||||
local function is_highlight_name(capture_name)
|
||||
local firstc = string.sub(capture_name, 1, 1)
|
||||
return firstc ~= string.lower(firstc)
|
||||
end
|
||||
|
||||
---@private
|
||||
function TSHighlighterQuery.new(lang, query_string)
|
||||
local self = setmetatable({}, { __index = TSHighlighterQuery })
|
||||
|
||||
self.hl_cache = setmetatable({}, {
|
||||
__index = function(table, capture)
|
||||
local hl, is_vim_highlight = self:_get_hl_from_capture(capture)
|
||||
if not is_vim_highlight then
|
||||
hl = _link_default_highlight_once(lang .. hl, hl)
|
||||
local name = self._query.captures[capture]
|
||||
local id = 0
|
||||
if not vim.startswith(name, '_') then
|
||||
id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang)
|
||||
end
|
||||
|
||||
local id = a.nvim_get_hl_id_by_name(hl)
|
||||
|
||||
rawset(table, capture, id)
|
||||
return id
|
||||
end,
|
||||
@@ -130,20 +43,6 @@ function TSHighlighterQuery:query()
|
||||
return self._query
|
||||
end
|
||||
|
||||
---@private
|
||||
--- Get the hl from capture.
|
||||
--- Returns a tuple { highlight_name: string, is_builtin: bool }
|
||||
function TSHighlighterQuery:_get_hl_from_capture(capture)
|
||||
local name = self._query.captures[capture]
|
||||
|
||||
if is_highlight_name(name) then
|
||||
-- From "Normal.left" only keep "Normal"
|
||||
return vim.split(name, '.', true)[1], true
|
||||
else
|
||||
return TSHighlighter.hl_map[name] or 0, false
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a new highlighter using @param tree
|
||||
---
|
||||
---@param tree The language tree to use for highlighting
|
||||
|
@@ -2059,7 +2059,7 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***fil
|
||||
{ EXPAND_MENUNAMES, get_menu_names, false, true },
|
||||
{ EXPAND_SYNTAX, get_syntax_name, true, true },
|
||||
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
|
||||
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
|
||||
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false },
|
||||
{ EXPAND_EVENTS, expand_get_event_name, true, false },
|
||||
{ EXPAND_AUGROUP, expand_get_augroup_name, true, false },
|
||||
{ EXPAND_CSCOPE, get_cscope_name, true, true },
|
||||
|
@@ -79,6 +79,8 @@ typedef struct {
|
||||
int sg_rgb_sp_idx; ///< RGB special color index
|
||||
|
||||
int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
|
||||
|
||||
int sg_parent; ///< parent of @nested.group
|
||||
} HlGroup;
|
||||
|
||||
enum {
|
||||
@@ -183,6 +185,54 @@ static const char *highlight_init_both[] = {
|
||||
"default link DiagnosticSignWarn DiagnosticWarn",
|
||||
"default link DiagnosticSignInfo DiagnosticInfo",
|
||||
"default link DiagnosticSignHint DiagnosticHint",
|
||||
|
||||
"default link @error Error",
|
||||
"default link @text.underline Underlined",
|
||||
"default link @todo Todo",
|
||||
"default link @debug Debug",
|
||||
|
||||
// Miscs
|
||||
"default link @comment Comment",
|
||||
"default link @punctuation Delimiter",
|
||||
|
||||
// Constants
|
||||
"default link @constant Constant",
|
||||
"default link @constant.builtin Special",
|
||||
"default link @constant.macro Define",
|
||||
"default link @define Define",
|
||||
"default link @macro Macro",
|
||||
"default link @string String",
|
||||
"default link @string.escape SpecialChar",
|
||||
"default link @character Character",
|
||||
"default link @character.special SpecialChar",
|
||||
"default link @number Number",
|
||||
"default link @boolean Boolean",
|
||||
"default link @float Float",
|
||||
|
||||
// Functions
|
||||
"default link @function Function",
|
||||
"default link @function.builtin Special",
|
||||
"default link @function.macro Macro",
|
||||
"default link @parameter Identifier",
|
||||
"default link @method Function",
|
||||
"default link @field Identifier",
|
||||
"default link @property Identifier",
|
||||
"default link @constructor Special",
|
||||
|
||||
// Keywords
|
||||
"default link @conditional Conditional",
|
||||
"default link @repeat Repeat",
|
||||
"default link @label Label",
|
||||
"default link @operator Operator",
|
||||
"default link @keyword Keyword",
|
||||
"default link @exception Exception",
|
||||
|
||||
"default link @type Type",
|
||||
"default link @type.definition Typedef",
|
||||
"default link @storageclass StorageClass",
|
||||
"default link @structure Structure",
|
||||
"default link @include Include",
|
||||
"default link @preproc PreProc",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -1375,6 +1425,11 @@ static void highlight_list_one(const int id)
|
||||
return;
|
||||
}
|
||||
|
||||
// don't list specialized groups if a parent is used instead
|
||||
if (sgp->sg_parent && sgp->sg_cleared) {
|
||||
return;
|
||||
}
|
||||
|
||||
didh = highlight_list_arg(id, didh, LIST_ATTR,
|
||||
sgp->sg_cterm, NULL, "cterm");
|
||||
didh = highlight_list_arg(id, didh, LIST_INT,
|
||||
@@ -1661,7 +1716,12 @@ static void set_hl_attr(int idx)
|
||||
int syn_name2id(const char *name)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
return syn_name2id_len(name, STRLEN(name));
|
||||
if (name[0] == '@') {
|
||||
// if we look up @aaa.bbb, we have to consider @aaa as well
|
||||
return syn_check_group(name, strlen(name));
|
||||
} else {
|
||||
return syn_name2id_len(name, STRLEN(name));
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup a highlight group name and return its ID.
|
||||
@@ -1758,6 +1818,14 @@ static int syn_add_group(const char *name, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
int scoped_parent = 0;
|
||||
if (len > 1 && name[0] == '@') {
|
||||
char *delim = xmemrchr(name, '.', len);
|
||||
if (delim) {
|
||||
scoped_parent = syn_check_group(name, (size_t)(delim - name));
|
||||
}
|
||||
}
|
||||
|
||||
// First call for this growarray: init growing array.
|
||||
if (highlight_ga.ga_data == NULL) {
|
||||
highlight_ga.ga_itemsize = sizeof(HlGroup);
|
||||
@@ -1783,6 +1851,9 @@ static int syn_add_group(const char *name, size_t len)
|
||||
hlgp->sg_rgb_sp_idx = kColorIdxNone;
|
||||
hlgp->sg_blend = -1;
|
||||
hlgp->sg_name_u = arena_memdupz(&highlight_arena, name, len);
|
||||
hlgp->sg_parent = scoped_parent;
|
||||
// will get set to false by caller if settings are added
|
||||
hlgp->sg_cleared = true;
|
||||
vim_strup((char_u *)hlgp->sg_name_u);
|
||||
|
||||
int id = highlight_ga.ga_len; // ID is index plus one
|
||||
@@ -1844,10 +1915,13 @@ int syn_ns_get_final_id(int *ns_id, int hl_id)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) {
|
||||
if (sgp->sg_link > 0 && sgp->sg_link <= highlight_ga.ga_len) {
|
||||
hl_id = sgp->sg_link;
|
||||
} else if (sgp->sg_cleared && sgp->sg_parent > 0) {
|
||||
hl_id = sgp->sg_parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
hl_id = sgp->sg_link;
|
||||
}
|
||||
|
||||
return hl_id;
|
||||
|
@@ -244,7 +244,7 @@ func Test_match_completion()
|
||||
return
|
||||
endif
|
||||
hi Aardig ctermfg=green
|
||||
call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call feedkeys(":match A\<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call assert_equal('"match Aardig', getreg(':'))
|
||||
call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
|
||||
call assert_equal('"match none', getreg(':'))
|
||||
@@ -255,9 +255,7 @@ func Test_highlight_completion()
|
||||
return
|
||||
endif
|
||||
hi Aardig ctermfg=green
|
||||
call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call assert_equal('"hi Aardig', getreg(':'))
|
||||
call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call assert_equal('"hi default Aardig', getreg(':'))
|
||||
call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
|
||||
call assert_equal('"hi clear Aardig', getreg(':'))
|
||||
|
@@ -188,22 +188,22 @@ func Test_syntax_completion()
|
||||
call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:)
|
||||
|
||||
" Check that clearing "Aap" avoids it showing up before Boolean.
|
||||
hi Aap ctermfg=blue
|
||||
hi @Aap ctermfg=blue
|
||||
call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
|
||||
call assert_match('^"syn list Aap Boolean Character ', @:)
|
||||
hi clear Aap
|
||||
call assert_match('^"syn list @Aap @boolean @character ', @:)
|
||||
hi clear @Aap
|
||||
|
||||
call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
|
||||
call assert_match('^"syn list Boolean Character ', @:)
|
||||
call assert_match('^"syn list @boolean @character ', @:)
|
||||
|
||||
call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx')
|
||||
call assert_match('^"syn match Boolean Character ', @:)
|
||||
call assert_match('^"syn match @boolean @character ', @:)
|
||||
endfunc
|
||||
|
||||
func Test_echohl_completion()
|
||||
call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
|
||||
" call assert_equal('"echohl NonText Normal none', @:)
|
||||
call assert_equal('"echohl NonText Normal NormalFloat NormalNC none', @:)
|
||||
call assert_equal('"echohl NonText Normal NormalFloat none', @:)
|
||||
endfunc
|
||||
|
||||
func Test_syntax_arg_skipped()
|
||||
|
@@ -6,11 +6,14 @@ local insert = helpers.insert
|
||||
local exec_lua = helpers.exec_lua
|
||||
local feed = helpers.feed
|
||||
local pending_c_parser = helpers.pending_c_parser
|
||||
local command = helpers.command
|
||||
local meths = helpers.meths
|
||||
local eq = helpers.eq
|
||||
|
||||
before_each(clear)
|
||||
|
||||
local hl_query = [[
|
||||
(ERROR) @ErrorMsg
|
||||
(ERROR) @error
|
||||
|
||||
"if" @keyword
|
||||
"else" @keyword
|
||||
@@ -23,23 +26,24 @@ local hl_query = [[
|
||||
"enum" @type
|
||||
"extern" @type
|
||||
|
||||
(string_literal) @string.nonexistent-specializer-for-string.should-fallback-to-string
|
||||
; nonexistent specializer for string should fallback to string
|
||||
(string_literal) @string.nonexistent_specializer
|
||||
|
||||
(number_literal) @number
|
||||
(char_literal) @string
|
||||
|
||||
(type_identifier) @type
|
||||
((type_identifier) @Special (#eq? @Special "LuaRef"))
|
||||
((type_identifier) @constant.builtin (#eq? @constant.builtin "LuaRef"))
|
||||
|
||||
(primitive_type) @type
|
||||
(sized_type_specifier) @type
|
||||
|
||||
; Use lua regexes
|
||||
((identifier) @Identifier (#contains? @Identifier "lua_"))
|
||||
((identifier) @function (#contains? @function "lua_"))
|
||||
((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$"))
|
||||
((identifier) @Normal (#vim-match? @Constant "^lstate$"))
|
||||
((identifier) @Normal (#vim-match? @Normal "^lstate$"))
|
||||
|
||||
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
|
||||
((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right) (#eq? @warning.left @warning.right))
|
||||
|
||||
(comment) @comment
|
||||
]]
|
||||
@@ -103,6 +107,7 @@ describe('treesitter highlighting', function()
|
||||
}
|
||||
|
||||
exec_lua([[ hl_query = ... ]], hl_query)
|
||||
command [[ hi link @warning WarningMsg ]]
|
||||
end)
|
||||
|
||||
it('is updated with edits', function()
|
||||
@@ -547,7 +552,7 @@ describe('treesitter highlighting', function()
|
||||
|
||||
-- This will change ONLY the literal strings to look like comments
|
||||
-- The only literal string is the "vim.schedule: expected function" in this test.
|
||||
exec_lua [[vim.cmd("highlight link cString comment")]]
|
||||
exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]]
|
||||
screen:expect{grid=[[
|
||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||
@@ -612,6 +617,11 @@ describe('treesitter highlighting', function()
|
||||
-- bold will not be overwritten at the moment
|
||||
[12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100};
|
||||
}}
|
||||
|
||||
eq({
|
||||
{capture='Error', priority='101'};
|
||||
{capture='type'};
|
||||
}, exec_lua [[ return vim.treesitter.get_captures_at_position(0, 0, 2) ]])
|
||||
end)
|
||||
|
||||
it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function()
|
||||
@@ -642,11 +652,13 @@ describe('treesitter highlighting', function()
|
||||
|
|
||||
]]}
|
||||
|
||||
command [[
|
||||
hi link @foo.bar Type
|
||||
hi link @foo String
|
||||
]]
|
||||
exec_lua [[
|
||||
local parser = vim.treesitter.get_parser(0, "c", {})
|
||||
local highlighter = vim.treesitter.highlighter
|
||||
highlighter.hl_map['foo.bar'] = 'Type'
|
||||
highlighter.hl_map['foo'] = 'String'
|
||||
test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}})
|
||||
]]
|
||||
|
||||
@@ -670,6 +682,29 @@ describe('treesitter highlighting', function()
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
-- clearing specialization reactivates fallback
|
||||
command [[ hi clear @foo.bar ]]
|
||||
screen:expect{grid=[[
|
||||
{5:char}* x = {5:"Will somebody ever read this?"}; |
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
end)
|
||||
|
||||
it("supports conceal attribute", function()
|
||||
@@ -712,32 +747,26 @@ describe('treesitter highlighting', function()
|
||||
]]}
|
||||
end)
|
||||
|
||||
it("hl_map has the correct fallback behavior", function()
|
||||
exec_lua [[
|
||||
local hl_map = vim.treesitter.highlighter.hl_map
|
||||
hl_map["foo"] = 1
|
||||
hl_map["foo.bar"] = 2
|
||||
hl_map["foo.bar.baz"] = 3
|
||||
it("@foo.bar groups has the correct fallback behavior", function()
|
||||
local get_hl = function(name) return meths.get_hl_by_name(name,1).foreground end
|
||||
meths.set_hl(0, "@foo", {fg = 1})
|
||||
meths.set_hl(0, "@foo.bar", {fg = 2})
|
||||
meths.set_hl(0, "@foo.bar.baz", {fg = 3})
|
||||
|
||||
assert(hl_map["foo"] == 1)
|
||||
assert(hl_map["foo.a.b.c.d"] == 1)
|
||||
assert(hl_map["foo.bar"] == 2)
|
||||
assert(hl_map["foo.bar.a.b.c.d"] == 2)
|
||||
assert(hl_map["foo.bar.baz"] == 3)
|
||||
assert(hl_map["foo.bar.baz.d"] == 3)
|
||||
eq(1, get_hl"@foo")
|
||||
eq(1, get_hl"@foo.a.b.c.d")
|
||||
eq(2, get_hl"@foo.bar")
|
||||
eq(2, get_hl"@foo.bar.a.b.c.d")
|
||||
eq(3, get_hl"@foo.bar.baz")
|
||||
eq(3, get_hl"@foo.bar.baz.d")
|
||||
|
||||
hl_map["FOO"] = 1
|
||||
hl_map["FOO.BAR"] = 2
|
||||
assert(hl_map["FOO.BAR.BAZ"] == 2)
|
||||
|
||||
hl_map["foo.missing.exists"] = 3
|
||||
assert(hl_map["foo.missing"] == 1)
|
||||
assert(hl_map["foo.missing.exists"] == 3)
|
||||
assert(hl_map["foo.missing.exists.bar"] == 3)
|
||||
assert(hl_map["total.nonsense.but.a.lot.of.dots"] == nil)
|
||||
-- It will not perform a second look up of this variable but return a sentinel value
|
||||
assert(hl_map["total.nonsense.but.a.lot.of.dots"] == "__notfound")
|
||||
]]
|
||||
-- lookup is case insensitive
|
||||
eq(2, get_hl"@FOO.BAR.SPAM")
|
||||
|
||||
meths.set_hl(0, "@foo.missing.exists", {fg = 3})
|
||||
eq(1, get_hl"@foo.missing")
|
||||
eq(3, get_hl"@foo.missing.exists")
|
||||
eq(3, get_hl"@foo.missing.exists.bar")
|
||||
eq(nil, get_hl"@total.nonsense.but.a.lot.of.dots")
|
||||
end)
|
||||
end)
|
||||
|
Reference in New Issue
Block a user