aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/doc/treesitter.txt16
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua5
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua7
-rw-r--r--runtime/lua/vim/treesitter/query.lua66
-rw-r--r--src/nvim/drawscreen.c7
-rw-r--r--src/nvim/ex_getln.c54
-rw-r--r--test/functional/lua/ui_event_spec.lua4
-rw-r--r--test/functional/treesitter/query_spec.lua28
9 files changed, 142 insertions, 47 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 1dee72314a..65417971ab 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -376,6 +376,8 @@ TREESITTER
child that contains a given node as descendant.
• |LanguageTree:parse()| optionally supports asynchronous invocation, which is
activated by passing the `on_parse` callback parameter.
+• |vim.treesitter.query.set()| can now inherit and/or extend runtime file
+ queries in addition to overriding.
TUI
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index d87439a1e0..df974d750c 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -1349,7 +1349,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()*
`info.captures`).
• `info.patterns`: information about predicates.
- Example (to try it, use `g==` or select the code then run `:'<,'>lua`): >lua
+ Example: >lua
local query = vim.treesitter.query.parse('vimdoc', [[
; query
((h1) @str
@@ -1466,8 +1466,18 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts})
set({lang}, {query_name}, {text}) *vim.treesitter.query.set()*
Sets the runtime query named {query_name} for {lang}
- This allows users to override any runtime files and/or configuration set
- by plugins.
+ This allows users to override or extend any runtime files and/or
+ configuration set by plugins.
+
+ For example, you could enable spellchecking of `C` identifiers with the
+ following code: >lua
+ vim.treesitter.query.set(
+ 'c',
+ 'highlights',
+ [[;inherits c
+ (identifier) @spell]])
+ ]])
+<
Parameters: ~
• {lang} (`string`) Language to use for the query
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index c11fa1999d..6dd47811bd 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -160,7 +160,10 @@ function TSHighlighter:destroy()
vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
vim.b[self.bufnr].ts_highlight = nil
if vim.g.syntax_on == 1 then
- api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr })
+ api.nvim_exec_autocmds(
+ 'FileType',
+ { group = 'syntaxset', buffer = self.bufnr, modeline = false }
+ )
end
end
end
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 5e1156fa68..8ea1c44cdc 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -475,13 +475,18 @@ function LanguageTree:_async_parse(range, on_parse)
return
end
- local buf = vim.b[self._source]
+ local source = self._source
+ local buf = vim.b[source]
local ct = buf.changedtick
local total_parse_time = 0
local redrawtime = vim.o.redrawtime
local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil
local function step()
+ if type(source) == 'number' and not vim.api.nvim_buf_is_valid(source) then
+ return nil
+ end
+
-- If buffer was changed in the middle of parsing, reset parse state
if buf.changedtick ~= ct then
ct = buf.changedtick
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 8055270a7f..10fb82e533 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -5,6 +5,9 @@ local api = vim.api
local language = require('vim.treesitter.language')
local memoize = vim.func._memoize
+local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$'
+local EXTENDS_FORMAT = '^;+%s*extends%s*$'
+
local M = {}
local function is_directive(name)
@@ -167,9 +170,6 @@ function M.get_files(lang, query_name, is_included)
-- ;+ inherits: ({language},)*{language}
--
-- {language} ::= {lang} | ({lang})
- local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$'
- local EXTENDS_FORMAT = '^;+%s*extends%s*$'
-
for _, filename in ipairs(lang_files) do
local file, err = io.open(filename, 'r')
if not file then
@@ -242,8 +242,8 @@ local function read_query_files(filenames)
return table.concat(contents, '')
end
--- The explicitly set queries from |vim.treesitter.query.set()|
----@type table<string,table<string,vim.treesitter.Query>>
+-- The explicitly set query strings from |vim.treesitter.query.set()|
+---@type table<string,table<string,string>>
local explicit_queries = setmetatable({}, {
__index = function(t, k)
local lang_queries = {}
@@ -255,16 +255,27 @@ local explicit_queries = setmetatable({}, {
--- Sets the runtime query named {query_name} for {lang}
---
---- This allows users to override any runtime files and/or configuration
+--- This allows users to override or extend any runtime files and/or configuration
--- set by plugins.
---
+--- For example, you could enable spellchecking of `C` identifiers with the
+--- following code:
+--- ```lua
+--- vim.treesitter.query.set(
+--- 'c',
+--- 'highlights',
+--- [[;inherits c
+--- (identifier) @spell]])
+--- ]])
+--- ```
+---
---@param lang string Language to use for the query
---@param query_name string Name of the query (e.g., "highlights")
---@param text string Query text (unparsed).
function M.set(lang, query_name, text)
--- @diagnostic disable-next-line: undefined-field LuaLS bad at generics
M.get:clear(lang, query_name)
- explicit_queries[lang][query_name] = M.parse(lang, text)
+ explicit_queries[lang][query_name] = text
end
--- Returns the runtime query {query_name} for {lang}.
@@ -274,12 +285,43 @@ end
---
---@return vim.treesitter.Query? : Parsed query. `nil` if no query files are found.
M.get = memoize('concat-2', function(lang, query_name)
+ local query_string ---@type string
+
if explicit_queries[lang][query_name] then
- return explicit_queries[lang][query_name]
- end
+ local query_files = {}
+ local base_langs = {} ---@type string[]
+
+ for line in explicit_queries[lang][query_name]:gmatch('([^\n]*)\n?') do
+ if not vim.startswith(line, ';') then
+ break
+ end
+
+ local lang_list = line:match(MODELINE_FORMAT)
+ if lang_list then
+ for _, incl_lang in ipairs(vim.split(lang_list, ',')) do
+ local is_optional = incl_lang:match('%(.*%)')
- local query_files = M.get_files(lang, query_name)
- local query_string = read_query_files(query_files)
+ if is_optional then
+ add_included_lang(base_langs, lang, incl_lang:sub(2, #incl_lang - 1))
+ else
+ add_included_lang(base_langs, lang, incl_lang)
+ end
+ end
+ elseif line:match(EXTENDS_FORMAT) then
+ table.insert(base_langs, lang)
+ end
+ end
+
+ for _, base_lang in ipairs(base_langs) do
+ local base_files = M.get_files(base_lang, query_name, true)
+ vim.list_extend(query_files, base_files)
+ end
+
+ query_string = read_query_files(query_files) .. explicit_queries[lang][query_name]
+ else
+ local query_files = M.get_files(lang, query_name)
+ query_string = read_query_files(query_files)
+ end
if #query_string == 0 then
return nil
@@ -303,7 +345,7 @@ api.nvim_create_autocmd('OptionSet', {
--- - `captures`: a list of unique capture names defined in the query (alias: `info.captures`).
--- - `info.patterns`: information about predicates.
---
---- Example (to try it, use `g==` or select the code then run `:'<,'>lua`):
+--- Example:
--- ```lua
--- local query = vim.treesitter.query.parse('vimdoc', [[
--- ; query
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 66c9b2be29..4d7f80bf76 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -692,8 +692,11 @@ int update_screen(void)
decor_providers_invoke_end();
- // either cmdline is cleared, not drawn or mode is last drawn
- cmdline_was_last_drawn = false;
+ // Either cmdline is cleared, not drawn or mode is last drawn.
+ // This does not (necessarily) overwrite an external cmdline.
+ if (!ui_has(kUICmdline)) {
+ cmdline_was_last_drawn = false;
+ }
return OK;
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 1eecee2a38..b5fa05e5a4 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -679,6 +679,15 @@ static void init_ccline(int firstc, int indent)
}
}
+static void ui_ext_cmdline_hide(bool abort)
+{
+ if (ui_has(kUICmdline)) {
+ cmdline_was_last_drawn = false;
+ ccline.redraw_state = kCmdRedrawNone;
+ ui_call_cmdline_hide(ccline.level, abort);
+ }
+}
+
/// Internal entry point for cmdline mode.
///
/// @param count only used for incremental search
@@ -954,8 +963,7 @@ theend:
char *p = ccline.cmdbuff;
if (ui_has(kUICmdline)) {
- ccline.redraw_state = kCmdRedrawNone;
- ui_call_cmdline_hide(ccline.level, s->gotesc);
+ ui_ext_cmdline_hide(s->gotesc);
msg_ext_clear_later();
}
if (!cmd_silent) {
@@ -2209,14 +2217,19 @@ end:
return ccline.one_key ? 0 : command_line_changed(s);
}
-static int command_line_not_changed(CommandLineState *s)
+/// Trigger CursorMovedC autocommands.
+static void may_trigger_cursormovedc(CommandLineState *s)
{
- // Trigger CursorMovedC autocommands.
if (ccline.cmdpos != s->prev_cmdpos) {
trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC);
s->prev_cmdpos = ccline.cmdpos;
+ ccline.redraw_state = MAX(ccline.redraw_state, kCmdRedrawPos);
}
+}
+static int command_line_not_changed(CommandLineState *s)
+{
+ may_trigger_cursormovedc(s);
// Incremental searches for "/" and "?":
// Enter command_line_not_changed() when a character has been read but the
// command line did not change. Then we only search and redraw if something
@@ -2696,11 +2709,7 @@ static int command_line_changed(CommandLineState *s)
// Trigger CmdlineChanged autocommands.
do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-');
- // Trigger CursorMovedC autocommands.
- if (ccline.cmdpos != s->prev_cmdpos) {
- trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC);
- s->prev_cmdpos = ccline.cmdpos;
- }
+ may_trigger_cursormovedc(s);
const bool prev_cmdpreview = cmdpreview;
if (s->firstc == ':'
@@ -2741,7 +2750,6 @@ static int command_line_changed(CommandLineState *s)
static void abandon_cmdline(void)
{
dealloc_cmdbuff();
- ccline.redraw_state = kCmdRedrawNone;
if (msg_scrolled == 0) {
compute_cmdrow();
}
@@ -3385,7 +3393,7 @@ color_cmdline_error:
// when cmdline_star is true.
static void draw_cmdline(int start, int len)
{
- if (!color_cmdline(&ccline)) {
+ if (ccline.cmdbuff == NULL || !color_cmdline(&ccline)) {
return;
}
@@ -3491,8 +3499,7 @@ void ui_ext_cmdline_block_leave(void)
ui_call_cmdline_block_hide();
}
-/// Extra redrawing needed for redraw! and on ui_attach
-/// assumes "redrawcmdline()" will already be invoked
+/// Extra redrawing needed for redraw! and on ui_attach.
void cmdline_screen_cleared(void)
{
if (!ui_has(kUICmdline)) {
@@ -3515,6 +3522,7 @@ void cmdline_screen_cleared(void)
}
line = line->prev_ccline;
}
+ redrawcmd();
}
/// called by ui_flush, do what redraws necessary to keep cmdline updated.
@@ -3527,12 +3535,14 @@ void cmdline_ui_flush(void)
CmdlineInfo *line = &ccline;
while (level > 0 && line) {
if (line->level == level) {
- if (line->redraw_state == kCmdRedrawAll) {
+ CmdRedraw redraw_state = line->redraw_state;
+ line->redraw_state = kCmdRedrawNone;
+ if (redraw_state == kCmdRedrawAll) {
+ cmdline_was_last_drawn = true;
ui_ext_cmdline_show(line);
- } else if (line->redraw_state == kCmdRedrawPos) {
+ } else if (redraw_state == kCmdRedrawPos && cmdline_was_last_drawn) {
ui_call_cmdline_pos(line->cmdpos, line->level);
}
- line->redraw_state = kCmdRedrawNone;
level--;
}
line = line->prev_ccline;
@@ -3900,12 +3910,7 @@ void compute_cmdrow(void)
void cursorcmd(void)
{
- if (cmd_silent) {
- return;
- }
-
- if (ui_has(kUICmdline)) {
- ccline.redraw_state = MAX(ccline.redraw_state, kCmdRedrawPos);
+ if (cmd_silent || ui_has(kUICmdline)) {
return;
}
@@ -4507,10 +4512,7 @@ static int open_cmdwin(void)
curwin->w_cursor.col = ccline.cmdpos;
changed_line_abv_curs();
invalidate_botline(curwin);
- if (ui_has(kUICmdline)) {
- ccline.redraw_state = kCmdRedrawNone;
- ui_call_cmdline_hide(ccline.level, false);
- }
+ ui_ext_cmdline_hide(false);
redraw_later(curwin, UPD_SOME_VALID);
// No Ex mode here!
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index 80457555d4..ddb10127e4 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -202,7 +202,7 @@ describe('vim.ui_attach', function()
feed([[:call confirm("Save changes?", "&Yes\n&No\n&Cancel")<CR>]])
screen:expect({
grid = [[
- ^5 |
+ ^4 |
{1:~ }|*4
]],
cmdline = {
@@ -224,7 +224,7 @@ describe('vim.ui_attach', function()
feed('n')
screen:expect({
grid = [[
- ^5 |
+ ^4 |
{1:~ }|*4
]],
cmdline = { { abort = false } },
diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua
index 6bab171ee8..6db0ffe5a0 100644
--- a/test/functional/treesitter/query_spec.lua
+++ b/test/functional/treesitter/query_spec.lua
@@ -812,6 +812,34 @@ void ui_refresh(void)
)
end)
+ it('supports "; extends" modeline in custom queries', function()
+ insert('int zeero = 0;')
+ local result = exec_lua(function()
+ vim.treesitter.query.set(
+ 'c',
+ 'highlights',
+ [[; extends
+ (identifier) @spell]]
+ )
+ local query = vim.treesitter.query.get('c', 'highlights')
+ local parser = vim.treesitter.get_parser(0, 'c')
+ local root = parser:parse()[1]:root()
+ local res = {}
+ for id, node in query:iter_captures(root, 0) do
+ table.insert(res, { query.captures[id], vim.treesitter.get_node_text(node, 0) })
+ end
+ return res
+ end)
+ eq({
+ { 'type.builtin', 'int' },
+ { 'variable', 'zeero' },
+ { 'spell', 'zeero' },
+ { 'operator', '=' },
+ { 'number', '0' },
+ { 'punctuation.delimiter', ';' },
+ }, result)
+ end)
+
describe('Query:iter_captures', function()
it('includes metadata for all captured nodes #23664', function()
insert([[