diff options
-rw-r--r-- | runtime/doc/news.txt | 2 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 16 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 5 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 7 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 66 | ||||
-rw-r--r-- | src/nvim/drawscreen.c | 7 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 54 | ||||
-rw-r--r-- | test/functional/lua/ui_event_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/treesitter/query_spec.lua | 28 |
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([[ |