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 = [[ ... ]]
|
local query2 = [[ ... ]]
|
||||||
highlighter:set_query(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
|
*lua-treesitter-highlight-groups*
|
||||||
begin with an upper-case letter like `@WarningMsg` will map directly to this
|
The capture names, with `@` included, are directly usable as highlight groups.
|
||||||
highlight group, if defined. Also if the predicate begins with upper-case and
|
A fallback system is implemented, so that more specific groups fallback to
|
||||||
contains a dot only the part before the first will be interpreted as the
|
more generic ones. For instance, in a language that has separate doc
|
||||||
highlight group. As an example, this warns of a binary expression with two
|
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|: >
|
identical identifiers, highlighting both as |hl-WarningMsg|: >
|
||||||
|
|
||||||
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
|
((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right)
|
||||||
(eq? @WarningMsg.left @WarningMsg.right))
|
(eq? @warning.left @warning.right))
|
||||||
<
|
<
|
||||||
Treesitter Highlighting Priority *lua-treesitter-highlight-priority*
|
Treesitter Highlighting Priority *lua-treesitter-highlight-priority*
|
||||||
|
|
||||||
@@ -352,6 +370,18 @@ attribute: >
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.treesitter *lua-treesitter-core*
|
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_node_range({node_or_range}) *get_node_range()*
|
||||||
Get the node's range or unpack a range table
|
Get the node's range or unpack a range table
|
||||||
|
|
||||||
@@ -393,6 +423,14 @@ is_ancestor({dest}, {source}) *is_ancestor()*
|
|||||||
Return: ~
|
Return: ~
|
||||||
(boolean) True if dest is an ancestor of source
|
(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()*
|
node_contains({node}, {range}) *node_contains()*
|
||||||
Determines if a node contains a range
|
Determines if a node contains a range
|
||||||
|
|
||||||
|
@@ -154,6 +154,28 @@ function M.get_node_range(node_or_range)
|
|||||||
end
|
end
|
||||||
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
|
---Determines if a node contains a range
|
||||||
---@param node table The node
|
---@param node table The node
|
||||||
---@param range table The range
|
---@param range table The range
|
||||||
@@ -167,4 +189,56 @@ function M.node_contains(node, range)
|
|||||||
return start_fits and end_fits
|
return start_fits and end_fits
|
||||||
end
|
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
|
return M
|
||||||
|
@@ -12,105 +12,18 @@ TSHighlighterQuery.__index = TSHighlighterQuery
|
|||||||
|
|
||||||
local ns = a.nvim_create_namespace('treesitter/highlighter')
|
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
|
---@private
|
||||||
function TSHighlighterQuery.new(lang, query_string)
|
function TSHighlighterQuery.new(lang, query_string)
|
||||||
local self = setmetatable({}, { __index = TSHighlighterQuery })
|
local self = setmetatable({}, { __index = TSHighlighterQuery })
|
||||||
|
|
||||||
self.hl_cache = setmetatable({}, {
|
self.hl_cache = setmetatable({}, {
|
||||||
__index = function(table, capture)
|
__index = function(table, capture)
|
||||||
local hl, is_vim_highlight = self:_get_hl_from_capture(capture)
|
local name = self._query.captures[capture]
|
||||||
if not is_vim_highlight then
|
local id = 0
|
||||||
hl = _link_default_highlight_once(lang .. hl, hl)
|
if not vim.startswith(name, '_') then
|
||||||
|
id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang)
|
||||||
end
|
end
|
||||||
|
|
||||||
local id = a.nvim_get_hl_id_by_name(hl)
|
|
||||||
|
|
||||||
rawset(table, capture, id)
|
rawset(table, capture, id)
|
||||||
return id
|
return id
|
||||||
end,
|
end,
|
||||||
@@ -130,20 +43,6 @@ function TSHighlighterQuery:query()
|
|||||||
return self._query
|
return self._query
|
||||||
end
|
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
|
--- Creates a new highlighter using @param tree
|
||||||
---
|
---
|
||||||
---@param tree The language tree to use for highlighting
|
---@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_MENUNAMES, get_menu_names, false, true },
|
||||||
{ EXPAND_SYNTAX, get_syntax_name, true, true },
|
{ EXPAND_SYNTAX, get_syntax_name, true, true },
|
||||||
{ EXPAND_SYNTIME, get_syntime_arg, 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_EVENTS, expand_get_event_name, true, false },
|
||||||
{ EXPAND_AUGROUP, expand_get_augroup_name, true, false },
|
{ EXPAND_AUGROUP, expand_get_augroup_name, true, false },
|
||||||
{ EXPAND_CSCOPE, get_cscope_name, true, true },
|
{ EXPAND_CSCOPE, get_cscope_name, true, true },
|
||||||
|
@@ -79,6 +79,8 @@ typedef struct {
|
|||||||
int sg_rgb_sp_idx; ///< RGB special color index
|
int sg_rgb_sp_idx; ///< RGB special color index
|
||||||
|
|
||||||
int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
|
int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
|
||||||
|
|
||||||
|
int sg_parent; ///< parent of @nested.group
|
||||||
} HlGroup;
|
} HlGroup;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -183,6 +185,54 @@ static const char *highlight_init_both[] = {
|
|||||||
"default link DiagnosticSignWarn DiagnosticWarn",
|
"default link DiagnosticSignWarn DiagnosticWarn",
|
||||||
"default link DiagnosticSignInfo DiagnosticInfo",
|
"default link DiagnosticSignInfo DiagnosticInfo",
|
||||||
"default link DiagnosticSignHint DiagnosticHint",
|
"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
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1375,6 +1425,11 @@ static void highlight_list_one(const int id)
|
|||||||
return;
|
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,
|
didh = highlight_list_arg(id, didh, LIST_ATTR,
|
||||||
sgp->sg_cterm, NULL, "cterm");
|
sgp->sg_cterm, NULL, "cterm");
|
||||||
didh = highlight_list_arg(id, didh, LIST_INT,
|
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)
|
int syn_name2id(const char *name)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
|
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));
|
return syn_name2id_len(name, STRLEN(name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup a highlight group name and return its ID.
|
/// 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.
|
// First call for this growarray: init growing array.
|
||||||
if (highlight_ga.ga_data == NULL) {
|
if (highlight_ga.ga_data == NULL) {
|
||||||
highlight_ga.ga_itemsize = sizeof(HlGroup);
|
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_rgb_sp_idx = kColorIdxNone;
|
||||||
hlgp->sg_blend = -1;
|
hlgp->sg_blend = -1;
|
||||||
hlgp->sg_name_u = arena_memdupz(&highlight_arena, name, len);
|
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);
|
vim_strup((char_u *)hlgp->sg_name_u);
|
||||||
|
|
||||||
int id = highlight_ga.ga_len; // ID is index plus one
|
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;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
hl_id = sgp->sg_link;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hl_id;
|
return hl_id;
|
||||||
|
@@ -244,7 +244,7 @@ func Test_match_completion()
|
|||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
hi Aardig ctermfg=green
|
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 assert_equal('"match Aardig', getreg(':'))
|
||||||
call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
|
call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
|
||||||
call assert_equal('"match none', getreg(':'))
|
call assert_equal('"match none', getreg(':'))
|
||||||
@@ -255,9 +255,7 @@ func Test_highlight_completion()
|
|||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
hi Aardig ctermfg=green
|
hi Aardig ctermfg=green
|
||||||
call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt')
|
call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt')
|
||||||
call assert_equal('"hi Aardig', getreg(':'))
|
|
||||||
call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt')
|
|
||||||
call assert_equal('"hi default Aardig', getreg(':'))
|
call assert_equal('"hi default Aardig', getreg(':'))
|
||||||
call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
|
call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
|
||||||
call assert_equal('"hi clear Aardig', getreg(':'))
|
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', @:)
|
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.
|
" 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 feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
|
||||||
call assert_match('^"syn list Aap Boolean Character ', @:)
|
call assert_match('^"syn list @Aap @boolean @character ', @:)
|
||||||
hi clear Aap
|
hi clear @Aap
|
||||||
|
|
||||||
call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
|
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 feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx')
|
||||||
call assert_match('^"syn match Boolean Character ', @:)
|
call assert_match('^"syn match @boolean @character ', @:)
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_echohl_completion()
|
func Test_echohl_completion()
|
||||||
call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
|
call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
|
||||||
" call assert_equal('"echohl NonText Normal none', @:)
|
" 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
|
endfunc
|
||||||
|
|
||||||
func Test_syntax_arg_skipped()
|
func Test_syntax_arg_skipped()
|
||||||
|
@@ -6,11 +6,14 @@ local insert = helpers.insert
|
|||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
local feed = helpers.feed
|
local feed = helpers.feed
|
||||||
local pending_c_parser = helpers.pending_c_parser
|
local pending_c_parser = helpers.pending_c_parser
|
||||||
|
local command = helpers.command
|
||||||
|
local meths = helpers.meths
|
||||||
|
local eq = helpers.eq
|
||||||
|
|
||||||
before_each(clear)
|
before_each(clear)
|
||||||
|
|
||||||
local hl_query = [[
|
local hl_query = [[
|
||||||
(ERROR) @ErrorMsg
|
(ERROR) @error
|
||||||
|
|
||||||
"if" @keyword
|
"if" @keyword
|
||||||
"else" @keyword
|
"else" @keyword
|
||||||
@@ -23,23 +26,24 @@ local hl_query = [[
|
|||||||
"enum" @type
|
"enum" @type
|
||||||
"extern" @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
|
(number_literal) @number
|
||||||
(char_literal) @string
|
(char_literal) @string
|
||||||
|
|
||||||
(type_identifier) @type
|
(type_identifier) @type
|
||||||
((type_identifier) @Special (#eq? @Special "LuaRef"))
|
((type_identifier) @constant.builtin (#eq? @constant.builtin "LuaRef"))
|
||||||
|
|
||||||
(primitive_type) @type
|
(primitive_type) @type
|
||||||
(sized_type_specifier) @type
|
(sized_type_specifier) @type
|
||||||
|
|
||||||
; Use lua regexes
|
; Use lua regexes
|
||||||
((identifier) @Identifier (#contains? @Identifier "lua_"))
|
((identifier) @function (#contains? @function "lua_"))
|
||||||
((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$"))
|
((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
|
(comment) @comment
|
||||||
]]
|
]]
|
||||||
@@ -103,6 +107,7 @@ describe('treesitter highlighting', function()
|
|||||||
}
|
}
|
||||||
|
|
||||||
exec_lua([[ hl_query = ... ]], hl_query)
|
exec_lua([[ hl_query = ... ]], hl_query)
|
||||||
|
command [[ hi link @warning WarningMsg ]]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('is updated with edits', function()
|
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
|
-- This will change ONLY the literal strings to look like comments
|
||||||
-- The only literal string is the "vim.schedule: expected function" in this test.
|
-- 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=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||||
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
{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
|
-- bold will not be overwritten at the moment
|
||||||
[12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100};
|
[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)
|
end)
|
||||||
|
|
||||||
it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function()
|
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 [[
|
exec_lua [[
|
||||||
local parser = vim.treesitter.get_parser(0, "c", {})
|
local parser = vim.treesitter.get_parser(0, "c", {})
|
||||||
local highlighter = vim.treesitter.highlighter
|
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"}})
|
test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}})
|
||||||
]]
|
]]
|
||||||
|
|
||||||
@@ -670,6 +682,29 @@ describe('treesitter highlighting', function()
|
|||||||
{1:~ }|
|
{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)
|
end)
|
||||||
|
|
||||||
it("supports conceal attribute", function()
|
it("supports conceal attribute", function()
|
||||||
@@ -712,32 +747,26 @@ describe('treesitter highlighting', function()
|
|||||||
]]}
|
]]}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("hl_map has the correct fallback behavior", function()
|
it("@foo.bar groups has the correct fallback behavior", function()
|
||||||
exec_lua [[
|
local get_hl = function(name) return meths.get_hl_by_name(name,1).foreground end
|
||||||
local hl_map = vim.treesitter.highlighter.hl_map
|
meths.set_hl(0, "@foo", {fg = 1})
|
||||||
hl_map["foo"] = 1
|
meths.set_hl(0, "@foo.bar", {fg = 2})
|
||||||
hl_map["foo.bar"] = 2
|
meths.set_hl(0, "@foo.bar.baz", {fg = 3})
|
||||||
hl_map["foo.bar.baz"] = 3
|
|
||||||
|
|
||||||
assert(hl_map["foo"] == 1)
|
eq(1, get_hl"@foo")
|
||||||
assert(hl_map["foo.a.b.c.d"] == 1)
|
eq(1, get_hl"@foo.a.b.c.d")
|
||||||
assert(hl_map["foo.bar"] == 2)
|
eq(2, get_hl"@foo.bar")
|
||||||
assert(hl_map["foo.bar.a.b.c.d"] == 2)
|
eq(2, get_hl"@foo.bar.a.b.c.d")
|
||||||
assert(hl_map["foo.bar.baz"] == 3)
|
eq(3, get_hl"@foo.bar.baz")
|
||||||
assert(hl_map["foo.bar.baz.d"] == 3)
|
eq(3, get_hl"@foo.bar.baz.d")
|
||||||
|
|
||||||
hl_map["FOO"] = 1
|
-- lookup is case insensitive
|
||||||
hl_map["FOO.BAR"] = 2
|
eq(2, get_hl"@FOO.BAR.SPAM")
|
||||||
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")
|
|
||||||
]]
|
|
||||||
|
|
||||||
|
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)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user