From c4eb0b64bd4923a72fe737837cfe234c80fb539c Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Mon, 27 May 2024 13:20:03 +0200 Subject: fix(treesitter): find buffer in multiple windows #28922 Problem: 1. When interacting with multiple :InspectTree and the source buffer windows there is a high chance of errors due to the window ids not being updated and validated. 2. Not all InspectTree windows were closed when the source buffer was closed. Solution: 1. Update InspectTree window id on `CursorMoved` event and validate source buffer window id before trying to navigate to it. 2. Close all InspectTree windows --- runtime/lua/vim/treesitter/dev.lua | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 5c91f101c0..ca8cf85eda 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -325,7 +325,10 @@ function M.inspect_tree(opts) opts = opts or {} + -- source buffer local buf = api.nvim_get_current_buf() + + -- window id for source buffer local win = api.nvim_get_current_win() local treeview = assert(TSTreeView:new(buf, opts.lang)) @@ -334,12 +337,14 @@ function M.inspect_tree(opts) close_win(vim.b[buf].dev_inspect) end + -- window id for tree buffer local w = opts.winid if not w then vim.cmd(opts.command or '60vnew') w = api.nvim_get_current_win() end + -- tree buffer local b = opts.bufnr if b then api.nvim_win_set_buf(w, b) @@ -375,6 +380,12 @@ function M.inspect_tree(opts) callback = function() local row = api.nvim_win_get_cursor(w)[1] local lnum, col = treeview:get(row).node:start() + + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + api.nvim_set_current_win(win) api.nvim_win_set_cursor(win, { lnum + 1, col }) end, @@ -432,6 +443,7 @@ function M.inspect_tree(opts) return true end + w = api.nvim_get_current_win() api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] local lnum, col, end_lnum, end_col = treeview:get(row).node:range() @@ -441,6 +453,11 @@ function M.inspect_tree(opts) hl_group = 'Visual', }) + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) -- Move the cursor if highlighted range is completely out of view @@ -506,7 +523,10 @@ function M.inspect_tree(opts) buffer = buf, once = true, callback = function() - close_win(w) + -- close all tree windows + for _, window in pairs(vim.fn.win_findbuf(b)) do + close_win(window) + end end, }) end -- cgit From 8cbb1f20e557461c8417583a7f69d53aaaef920b Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Tue, 4 Jun 2024 09:06:02 -0400 Subject: refactor(lua): use tuple syntax everywhere #29111 --- runtime/lua/vim/treesitter/dev.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index ca8cf85eda..56608bbf14 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -174,7 +174,7 @@ end --- @param source_buf integer --- @param inspect_buf integer --- @param inspect_win integer ---- @param pos? { [1]: integer, [2]: integer } +--- @param pos? [integer, integer] local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos) api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1) -- cgit From 5e49ef0af3cb8dba658e5d0dc6a807f8edebf590 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 11 Jun 2024 12:05:18 +0100 Subject: refactor(lua): improve type annotations --- runtime/lua/vim/treesitter/_fold.lua | 1 - runtime/lua/vim/treesitter/_meta.lua | 2 ++ runtime/lua/vim/treesitter/highlighter.lua | 6 +++--- runtime/lua/vim/treesitter/languagetree.lua | 10 +++++----- 4 files changed, 10 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index eecf1ad6b1..04a3c62cf1 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -383,7 +383,6 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, end end ----@package ---@param lnum integer|nil ---@return string function M.foldexpr(lnum) diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 177699a207..2aedf5754e 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -33,6 +33,7 @@ error('Cannot require a meta file') ---@field iter_children fun(self: TSNode): fun(): TSNode, string ---@field field fun(self: TSNode, name: string): TSNode[] ---@field byte_length fun(self: TSNode): integer +---@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean local TSNode = {} ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) @@ -62,6 +63,7 @@ local TSNode = {} ---@field patterns table --- @param lang string +--- @return table vim._ts_inspect_language = function(lang) end ---@return integer diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d2f986b874..003f7e0169 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -47,7 +47,7 @@ function TSHighlighterQuery:get_hl_from_capture(capture) return self.hl_cache[capture] end ----@package +---@nodoc function TSHighlighterQuery:query() return self._query end @@ -75,7 +75,7 @@ local TSHighlighter = { TSHighlighter.__index = TSHighlighter ----@package +---@nodoc --- --- Creates a highlighter for `tree`. --- @@ -232,7 +232,7 @@ function TSHighlighter:on_changedtree(changes) end --- Gets the query used for @param lang ----@package +---@nodoc ---@param lang string Language used by the highlighter. ---@return vim.treesitter.highlighter.Query function TSHighlighter:get_query(lang) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b0812123b9..3523ea95e0 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -98,9 +98,9 @@ local LanguageTree = {} LanguageTree.__index = LanguageTree ---- @package +--- @nodoc --- ---- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any +--- LanguageTree contains a tree of parsers: the root treesitter parser for {lang} and any --- "injected" language parsers, which themselves may inject other languages, recursively. --- ---@param source (integer|string) Buffer or text string to parse @@ -951,7 +951,7 @@ function LanguageTree:_edit( end end ----@package +---@nodoc ---@param bufnr integer ---@param changed_tick integer ---@param start_row integer @@ -1023,12 +1023,12 @@ function LanguageTree:_on_bytes( ) end ----@package +---@nodoc function LanguageTree:_on_reload() self:invalidate(true) end ----@package +---@nodoc function LanguageTree:_on_detach(...) self:invalidate(true) self:_do_callback('detach', ...) -- cgit From 0e3e1e6b6d8370f1fcc9887d5cb931b131450a1c Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Thu, 20 Jun 2024 22:37:09 +0900 Subject: fix(treesitter): don't open fold when o/O adds a line below #28709 Problem: `o`-ing on a folded line opens the fold, because the new line gets the fold level from the above line (level '='), which extends the fold to the new line. `O` has a similar problem when run on the line below a fold. Solution: Use -1 for the added line to get the lower level from the above/below line. --- runtime/lua/vim/treesitter/_fold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 04a3c62cf1..b8f1ef1282 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -87,7 +87,7 @@ end ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) - list_insert(self.levels, srow + 1, erow, '=') + list_insert(self.levels, srow + 1, erow, -1) list_insert(self.levels0, srow + 1, erow, -1) end -- cgit From da4e8dc5b04a82c6dd483f6c5345a81d8b375bec Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sun, 23 Jun 2024 13:50:21 +0200 Subject: fix(treesitter): do not modify highlight state for _on_spell_nav Problem: Treesitter highlighter clears the already populated highlight state when performing spell checking while drawing a smoothscrolled topline. Solution: Save and restore the highlight state in the highlighter's _on_spell_nav callback. --- runtime/lua/vim/treesitter/highlighter.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 003f7e0169..cd5c67d816 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -377,11 +377,15 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) return end + -- Do not affect potentially populated highlight state. Here we just want a temporary + -- empty state so the C code can detect whether the region should be spell checked. + local highlight_states = self._highlight_states self:prepare_highlight_states(srow, erow) for row = srow, erow do on_line_impl(self, buf, row, true) end + self._highlight_states = highlight_states end ---@private -- cgit From c57a85e0eda067ea28ca5853358947332aceecfd Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 14 Jun 2024 17:54:10 -0700 Subject: perf(treesitter): remove unnecessary foldexpr loop Instead of looping over all captured nodes, just take the end range from the last node in the list. This uses the fact that nodes returned by iter_matches are ordered by their range (earlier to later). --- runtime/lua/vim/treesitter/_fold.lua | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index b8f1ef1282..194e09babc 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -139,16 +139,12 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) local range = ts.get_range(nodes[1], bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) - for i = 2, #nodes, 1 do - local node_range = ts.get_range(nodes[i], bufnr, metadata[id]) - local node_start, _, node_stop, node_stop_col = Range.unpack4(node_range) - if node_start < start then - start = node_start - end - if node_stop > stop then - stop = node_stop - stop_col = node_stop_col - end + if #nodes > 1 then + -- assumes nodes are ordered by range + local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) + local _, _, end_stop, end_stop_col = Range.unpack4(end_range) + stop = end_stop + stop_col = end_stop_col end if stop_col == 0 then -- cgit From aa6b9c677d83d76d448c3bb0973bf8d14bfdf922 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 8 Jun 2024 21:40:18 +0200 Subject: refactor: use `vim._with` where possible This mostly means replacing `nvim_buf_call` and `nvim_win_call` with `vim._with`. --- runtime/lua/vim/treesitter/highlighter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index cd5c67d816..052b839609 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -143,7 +143,7 @@ function TSHighlighter.new(tree, opts) vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) end - api.nvim_buf_call(self.bufnr, function() + vim._with({ buf = self.bufnr }, function() vim.opt_local.spelloptions:append('noplainbuffer') end) -- cgit From d413038b4fd71b7a335b6653aa64d2cb6daeac7b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 3 Jul 2024 07:40:42 +0800 Subject: fix(treesitter): ensure syntaxset augroup exists (#29542) Problem: Error when calling vim.treesitter.start() and vim.treesitter.stop() in init.lua. Solution: Ensure syntaxset augroup exists after loading synload.vim. --- runtime/lua/vim/treesitter/highlighter.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 052b839609..a94c408f4e 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -139,8 +139,11 @@ function TSHighlighter.new(tree, opts) -- but use synload.vim rather than syntax.vim to not enable -- syntax FileType autocmds. Later on we should integrate with the -- `:syntax` and `set syntax=...` machinery properly. + -- Still need to ensure that syntaxset augroup exists, so that calling :destroy() + -- immediately afterwards will not error. if vim.g.syntax_on ~= 1 then vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) + vim.api.nvim_create_augroup('syntaxset', { clear = false }) end vim._with({ buf = self.bufnr }, function() -- cgit From 9217e0d671b790d39192181cb894cfec012f06a6 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 4 Jul 2024 10:09:19 -0700 Subject: fix(treesitter): display fields for anonymous nodes in :InspectTree --- runtime/lua/vim/treesitter/dev.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 56608bbf14..bd0ef5a011 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -220,14 +220,13 @@ function TSTreeView:draw(bufnr) local text ---@type string if item.node:named() then - if item.field then - text = string.format('%s: (%s', item.field, item.node:type()) - else - text = string.format('(%s', item.node:type()) - end + text = string.format('(%s', item.node:type()) else text = string.format('%q', item.node:type()):gsub('\n', 'n') end + if item.field then + text = string.format('%s: %s', item.field, text) + end local next = self:get(i + 1) if not next or next.depth <= item.depth then -- cgit From 8474f529780ba20f9179ad1b60167ee9e53f8374 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 6 Jul 2024 15:34:05 +0900 Subject: fix(treesitter.foldexpr): robustness against ctrl-c Problem: Exiting the insert mode with ctrl-c does not trigger InsertLeave autocmd. This may lead to nil error in treesitter foldexpr. Solution: Check nil. Folds still can be stale after exiting the insert mode with ctrl-c, but it will be eventually updated correctly. An alternative solution would be to ensure that exiting the insert mode always triggers do_foldupdate. This can be done either by "fixing" ctrl-c or with on_key callback that checks ctrl-c (nvim-cmp does this). --- runtime/lua/vim/treesitter/_fold.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 194e09babc..27590eff5d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -264,6 +264,15 @@ end ---@package function FoldInfo:do_foldupdate(bufnr) + -- InsertLeave is not executed when is used for exiting the insert mode, leaving + -- do_foldupdate untouched. If another execution of foldupdate consumes foldupdate_range, the + -- InsertLeave do_foldupdate gets nil foldupdate_range. In that case, skip the update. This is + -- correct because the update that consumed the range must have incorporated the range that + -- InsertLeave meant to update. + if not self.foldupdate_range then + return + end + local srow, erow = self.foldupdate_range[1], self.foldupdate_range[2] self.foldupdate_range = nil for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do -- cgit From 05dcda8f9b0583505327692570a2cfd0225124dc Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 14 Jul 2024 08:10:19 -0700 Subject: fix(treesitter): recognize aliased parsers in omnifunc, query linter **Problem:** A query file for something like `html_tags` will not be given html node completion **Solution:** Check for parser aliases before offering completions Co-authored-by: Lewis Russell --- runtime/lua/vim/treesitter/_query_linter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 12b4cbc7b9..6b8c3a17f2 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -40,7 +40,8 @@ end local function guess_query_lang(buf) local filename = api.nvim_buf_get_name(buf) if filename ~= '' then - return vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') + local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') + return resolved_filename and vim.treesitter.language.get_lang(resolved_filename) or nil end end -- cgit From bd3b6ec8360e0dd6edfe74c3d0013fd2b98b989e Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 28 Jul 2024 13:23:40 -0700 Subject: feat(treesitter): add node_for_range function This is identical to `named_node_for_range` except that it includes anonymous nodes. This maintains consistency in the API because we already have `descendant_for_range` and `named_descendant_for_range`. --- runtime/lua/vim/treesitter/languagetree.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 3523ea95e0..03803f5869 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -1133,6 +1133,18 @@ function LanguageTree:tree_for_range(range, opts) return nil end +--- Gets the smallest node that contains {range}. +--- +---@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts +---@return TSNode? +function LanguageTree:node_for_range(range, opts) + local tree = self:tree_for_range(range, opts) + if tree then + return tree:root():descendant_for_range(unpack(range)) + end +end + --- Gets the smallest named node that contains {range}. --- ---@param range Range4 `{ start_line, start_col, end_line, end_col }` -- cgit From 94d42a3e7239a4035ee4429cbd71d5b0162b8289 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 28 Jul 2024 13:42:18 -0700 Subject: fix(treesitter): highlight anonymous nodes in inspect_tree **Problem:** With anonymous nodes toggled in the inspect tree, only named nodes will be highlighted when moving the cursor in the source code buffer. **Solution:** Retrieve the anonymous node at the cursor (when toggled on in the inspect tree) and highlight them when appropriate, for better clarity/specificity. --- runtime/lua/vim/treesitter/dev.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index bd0ef5a011..e89aea2b85 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -183,6 +183,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins lang = lang, pos = pos, ignore_injections = false, + include_anonymous = treeview.opts.anon, }) if not cursor_node then return -- cgit From 0a1212ef94547f04db789a660639fab6837e00ce Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 6 Aug 2024 19:28:42 +0800 Subject: docs(treesitter): generate inline docs for `Range`s docs(treesitter): in-place parameter description docs(treesitter): remove internal type names docs(treesitter): add missing private annotation --- runtime/lua/vim/treesitter/_range.lua | 4 ++++ runtime/lua/vim/treesitter/languagetree.lua | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 8d727c3c52..82ab8517aa 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -3,16 +3,19 @@ local api = vim.api local M = {} ---@class Range2 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer end row ---@class Range4 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer start column ---@field [3] integer end row ---@field [4] integer end column ---@class Range6 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer start column ---@field [3] integer start bytes @@ -150,6 +153,7 @@ function M.contains(r1, r2) return true end +--- @private --- @param source integer|string --- @param index integer --- @return integer diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 03803f5869..d43a0a5d0b 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -638,6 +638,8 @@ end ---Gets the set of included regions managed by this LanguageTree. This can be different from the ---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions ---outside the requested range. +---Each list represents a range in the form of +---{ {start_row}, {start_col}, {start_bytes}, {end_row}, {end_col}, {end_bytes} }. ---@return table function LanguageTree:included_regions() if self._regions then @@ -1087,7 +1089,7 @@ end --- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do @@ -1108,7 +1110,7 @@ end --- Gets the tree that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts ---@return TSTree? function LanguageTree:tree_for_range(range, opts) @@ -1135,7 +1137,7 @@ end --- Gets the smallest node that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts ---@return TSNode? function LanguageTree:node_for_range(range, opts) @@ -1147,7 +1149,7 @@ end --- Gets the smallest named node that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts ---@return TSNode? function LanguageTree:named_node_for_range(range, opts) @@ -1159,7 +1161,7 @@ end --- Gets the appropriate language that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@return vim.treesitter.LanguageTree tree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do -- cgit From 688b961d13bd54a14836f08c3ded3121d3fb15a0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 19 Apr 2024 16:04:57 +0100 Subject: feat(treesitter): add support for wasm parsers Problem: Installing treesitter parser is hard (harder than climbing to heaven). Solution: Add optional support for wasm parsers with `wasmtime`. Notes: * Needs to be enabled by setting `ENABLE_WASMTIME` for tree-sitter and Neovim. Build with `make CMAKE_EXTRA_FLAGS=-DENABLE_WASMTIME=ON DEPS_CMAKE_FLAGS=-DENABLE_WASMTIME=ON` * Adds optional Rust (obviously) and C11 dependencies. * Wasmtime comes with a lot of features that can negatively affect Neovim performance due to library and symbol table size. Make sure to build with minimal features and full LTO. * To reduce re-compilation times, install `sccache` and build with `RUSTC_WRAPPER= make ...` --- runtime/lua/vim/treesitter/_meta.lua | 6 +++++- runtime/lua/vim/treesitter/health.lua | 3 +++ runtime/lua/vim/treesitter/language.lua | 9 ++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 2aedf5754e..39d1f1f5f8 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -72,7 +72,11 @@ vim._ts_get_language_version = function() end --- @param path string --- @param lang string --- @param symbol_name? string -vim._ts_add_language = function(path, lang, symbol_name) end +vim._ts_add_language_from_object = function(path, lang, symbol_name) end + +--- @param path string +--- @param lang string +vim._ts_add_language_from_wasm = function(path, lang) end ---@return integer vim._ts_get_minimum_language_version = function() end diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index ed3616ef46..637f9ea543 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -28,6 +28,9 @@ function M.check() ) end end + + local can_wasm = vim._ts_add_language_from_wasm ~= nil + health.info(string.format('Can load WASM parsers: %s', tostring(can_wasm))) end return M diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index d0a74daa6c..e31ce6cb59 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -109,7 +109,14 @@ function M.add(lang, opts) path = paths[1] end - vim._ts_add_language(path, lang, symbol_name) + if vim.endswith(path, '.wasm') then + if not vim._ts_add_language_from_wasm then + error(string.format("Unable to load wasm parser '%s': not built with ENABLE_WASMTIME ", path)) + end + vim._ts_add_language_from_wasm(path, lang) + else + vim._ts_add_language_from_object(path, lang, symbol_name) + end M.register(lang, filetype) end -- cgit From f8e1ebd6f62662129fdb54574b305189e4a0b7af Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Mon, 26 Aug 2024 20:40:37 -0700 Subject: fix(treesitter): escape things like `"` in omnifunc results --- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 6b8c3a17f2..8654b89c9b 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -241,7 +241,7 @@ function M.omnifunc(findstart, base) end end for _, s in pairs(parser_info.symbols) do - local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' ---@type string + local text = s[2] and s[1] or string.format('%q', s[1]):gsub('\n', 'n') ---@type string if text:find(base, 1, true) then table.insert(items, text) end -- cgit From 9b983e5f6cbfeaaf491ad57912518042be650b6d Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Sun, 1 Sep 2024 12:07:07 +0800 Subject: docs(treesitter): annotate some tables as `TSMetadata` --- runtime/lua/vim/treesitter/query.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ef5c2143a7..8e21353154 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -487,8 +487,8 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?'] ---@class vim.treesitter.query.TSMetadata ---@field range? Range ---@field conceal? string ----@field [integer] vim.treesitter.query.TSMetadata ----@field [string] integer|string +---@field [integer]? vim.treesitter.query.TSMetadata +---@field [string]? integer|string ---@alias TSDirective fun(match: table, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata) @@ -627,7 +627,7 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - see |vim.treesitter.query.add_directive()| for argument meanings ---@param opts vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) @@ -667,7 +667,7 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - match: A table mapping capture IDs to a list of captured nodes --- - pattern: the index of the matching pattern in the query file --- - predicate: list of strings containing the full directive being called, e.g. @@ -929,7 +929,7 @@ end --- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is --- incorrect behavior. This option will eventually become the default and removed. --- ----@return (fun(): integer, table, table): pattern id, match, metadata +---@return (fun(): integer, table, vim.treesitter.query.TSMetadata): pattern id, match, metadata function Query:iter_matches(node, source, start, stop, opts) opts = opts or {} opts.match_limit = opts.match_limit or 256 -- cgit From 6913c5e1d975a11262d08b3339d50b579e6b6bb8 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:01:53 -0500 Subject: feat(treesitter)!: default to correct behavior for quantified captures (#30193) For context, see https://github.com/neovim/neovim/pull/24738. Before that PR, Nvim did not correctly handle captures with quantifiers. That PR made the correct behavior opt-in to minimize breaking changes, with the intention that the correct behavior would eventually become the default. Users can still opt-in to the old (incorrect) behavior for now, but this option will eventually be removed completely. BREAKING CHANGE: Any plugin which uses `Query:iter_matches()` must update their call sites to expect an array of nodes in the `match` table, rather than a single node. --- runtime/lua/vim/treesitter/_fold.lua | 4 +--- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/languagetree.lua | 8 +------- runtime/lua/vim/treesitter/query.lua | 28 +++++++++++----------------- 4 files changed, 14 insertions(+), 28 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 27590eff5d..7375beaf9d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -131,9 +131,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) -- Collect folds starting from srow - 1, because we should first subtract the folds that end at -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for _, match, metadata in - query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true }) - do + for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do for id, nodes in pairs(match) do if query.captures[id] == 'fold' then local range = ts.get_range(nodes[1], bufnr, metadata[id]) diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 8654b89c9b..ea1ae5416a 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -176,7 +176,7 @@ function M.lint(buf, opts) parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then - for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1, { all = true }) do + for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do local lang_context = { lang = lang, parser_info = parser_info, diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index d43a0a5d0b..cc9ffeaa29 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -833,13 +833,7 @@ function LanguageTree:_get_injections() local start_line, _, end_line, _ = root_node:range() for pattern, match, metadata in - self._injection_query:iter_matches( - root_node, - self._source, - start_line, - end_line + 1, - { all = true } - ) + self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do local lang, combined, ranges = self:_get_injection(match, metadata) if lang then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8e21353154..bd0574b8b7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -620,8 +620,8 @@ local directive_handlers = { --- @field force? boolean --- --- Use the correct implementation of the match table where capture IDs map to ---- a list of nodes instead of a single node. Defaults to false (for backward ---- compatibility). This option will eventually become the default and removed. +--- a list of nodes instead of a single node. Defaults to true. This option will +--- be removed in a future release. --- @field all? boolean --- Adds a new predicate to be used in queries @@ -629,7 +629,7 @@ local directive_handlers = { ---@param name string Name of the predicate, without leading # ---@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param opts vim.treesitter.query.add_predicate.Opts +---@param opts? vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) -- Backward compatibility: old signature had "force" as boolean argument if type(opts) == 'boolean' then @@ -642,7 +642,7 @@ function M.add_predicate(name, handler, opts) error(string.format('Overriding existing predicate %s', name)) end - if opts.all then + if opts.all ~= false then predicate_handlers[name] = handler else --- @param match table @@ -894,16 +894,10 @@ end --- index of the pattern in the query, a table mapping capture indices to a list --- of nodes, and metadata from any directives processing the match. --- ---- WARNING: Set `all=true` to ensure all matching nodes in a match are ---- returned, otherwise only the last node in a match is returned, breaking captures ---- involving quantifiers such as `(comment)+ @comment`. The default option ---- `all=false` is only provided for backward compatibility and will be removed ---- after Nvim 0.10. ---- --- Example: --- --- ```lua ---- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do +--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1) do --- for id, nodes in pairs(match) do --- local name = query.captures[id] --- for _, node in ipairs(nodes) do @@ -925,9 +919,9 @@ end --- - max_start_depth (integer) if non-zero, sets the maximum start depth --- for each match. This is used to prevent traversing too deep into a tree. --- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256). ---- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes. ---- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is ---- incorrect behavior. This option will eventually become the default and removed. +--- - all (boolean) When `false` (default `true`), the returned table maps capture IDs to a single +--- (last) node instead of the full list of matching nodes. This option is only for backward +--- compatibility and will be removed in a future release. --- ---@return (fun(): integer, table, vim.treesitter.query.TSMetadata): pattern id, match, metadata function Query:iter_matches(node, source, start, stop, opts) @@ -960,10 +954,10 @@ function Query:iter_matches(node, source, start, stop, opts) local captures = match:captures() - if not opts.all then + if opts.all == false then -- Convert the match table into the old buggy version for backward - -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all" - -- option! + -- compatibility. This is slow, but we only do it when the caller explicitly opted into it by + -- setting `all` to `false`. local old_match = {} ---@type table for k, v in pairs(captures or {}) do old_match[k] = v[#v] -- cgit From b9b408a56c7e607972beaa7214719ff1494e384c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 13 Sep 2024 05:09:11 -0700 Subject: feat(treesitter): start moving get_parser to return nil #30313 **Problem:** `vim.treesitter.get_parser` will throw an error if no parser can be found. - This means the caller is responsible for wrapping it in a `pcall`, which is easy to forget - It also makes it slightly harder to potentially memoize `get_parser` in the future - It's a bit unintuitive since many other `get_*` style functions conventionally return `nil` if no object is found (e.g. `get_node`, `get_lang`, `query.get`, etc.) **Solution:** Return `nil` if no parser can be found or created - This requires a function signature change, and some new assertions in places where the parser will always (or should always) be found. - This commit starts by making this change internally, since it is breaking. Eventually it will be rolled out to the public API. --- runtime/lua/vim/treesitter/_fold.lua | 4 ++-- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 13 ++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7375beaf9d..49792c3891 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) - local parser = ts.get_parser(bufnr) + local parser = assert(ts._get_parser(bufnr)) parser:parse(parse_injections and { srow, erow } or nil) @@ -392,7 +392,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - local parser = vim.F.npcall(ts.get_parser, bufnr) + local parser = ts._get_parser(bufnr) if not parser then return '0' end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index ea1ae5416a..30acef559e 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -172,7 +172,7 @@ function M.lint(buf, opts) --- @type (table|nil) local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang) - local parser = vim.treesitter.get_parser(buf) + local parser = assert(vim.treesitter._get_parser(buf), 'query parser not found.') parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index e89aea2b85..606d5b27b9 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -76,10 +76,9 @@ end --- ---@package function TSTreeView:new(bufnr, lang) - local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) - if not ok then - local err = parser --[[ @as string ]] - return nil, 'No parser available for the given buffer:\n' .. err + local parser = vim.treesitter._get_parser(bufnr or 0, lang) + if not parser then + return nil, 'No parser available for the given buffer.' end -- For each child tree (injected language), find the root of the tree and locate the node within @@ -539,7 +538,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') local function update_editor_highlights(query_win, base_win, lang) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) - local parser = vim.treesitter.get_parser(base_buf, lang) + local parser = assert(vim.treesitter._get_parser(base_buf, lang)) api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') @@ -596,8 +595,8 @@ function M.edit_query(lang) end vim.cmd(cmd) - local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) - if not ok then + local parser = vim.treesitter._get_parser(buf, lang) + if not parser then return nil, 'No parser available for the given buffer' end lang = parser:lang() -- cgit From 052e048db676ef3e68efc497c02902e3d43e6255 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 19 Sep 2024 13:34:35 -0700 Subject: fix(treesitter): lint top-level anonymous nodes **Problem:** Top-level anonymous nodes are not being checked by the query linter **Solution:** Check them by adding them to the top-level query This commit also moves a table construction out of the match iterator so it is run less frequently. --- runtime/lua/vim/treesitter/_query_linter.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 30acef559e..417b8bafbb 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -65,7 +65,7 @@ local function normalize_opts(buf, opts) end local lint_query = [[;; query - (program [(named_node) (list) (grouping)] @toplevel) + (program [(named_node) (anonymous_node) (list) (grouping)] @toplevel) (named_node name: _ @node.named) (anonymous_node @@ -171,17 +171,17 @@ function M.lint(buf, opts) --- @type (table|nil) local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang) + local lang_context = { + lang = lang, + parser_info = parser_info, + is_first_lang = i == 1, + } local parser = assert(vim.treesitter._get_parser(buf), 'query parser not found.') parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do - local lang_context = { - lang = lang, - parser_info = parser_info, - is_first_lang = i == 1, - } lint_match(buf, match, query, lang_context, diagnostics) end end -- cgit From b63cd8cbaec1518b6580bf6e07e75a72c8779253 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 27 Sep 2024 03:27:00 -0700 Subject: fix(treesitter): EditQuery shows swapfile ATTENTION #30536 Problem: EditQuery shows swapfile ATTENTION, but this buffer is not intended for preservation (and the dialog breaks the UX). Solution: Set 'noswapfile' on the buffer before renaming it. --- runtime/lua/vim/treesitter/dev.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 606d5b27b9..2a6e166ef1 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -153,7 +153,7 @@ end ---@param w integer ---@param b integer -local function set_dev_properties(w, b) +local function set_dev_options(w, b) vim.wo[w].scrolloff = 5 vim.wo[w].wrap = false vim.wo[w].foldmethod = 'expr' @@ -164,6 +164,7 @@ local function set_dev_properties(w, b) vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' vim.bo[b].filetype = 'query' + vim.bo[b].swapfile = false end --- Updates the cursor position in the inspector to match the node under the cursor. @@ -354,7 +355,7 @@ function M.inspect_tree(opts) vim.b[buf].dev_inspect = w vim.b[b].dev_base = win -- base window handle vim.b[b].disable_query_linter = true - set_dev_properties(w, b) + set_dev_options(w, b) local title --- @type string? local opts_title = opts.title @@ -606,7 +607,7 @@ function M.edit_query(lang) vim.b[buf].dev_edit = query_win vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' - set_dev_properties(query_win, query_buf) + set_dev_options(query_win, query_buf) -- Note that omnifunc guesses the language based on the containing folder, -- so we add the parser's language to the buffer's name so that omnifunc -- cgit From 0f067cd34d09b38f9aaf2e1732d825e89b573077 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 14 Sep 2024 12:57:33 -0700 Subject: fix(treesitter): suppress get_parser warnings via opts.error --- runtime/lua/vim/treesitter/_fold.lua | 4 ++-- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 16 +++++++++++----- runtime/lua/vim/treesitter/query.lua | 6 +++++- 4 files changed, 19 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 49792c3891..7237d2e7d4 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) - local parser = assert(ts._get_parser(bufnr)) + local parser = assert(ts.get_parser(bufnr, nil, { error = false })) parser:parse(parse_injections and { srow, erow } or nil) @@ -392,7 +392,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - local parser = ts._get_parser(bufnr) + local parser = ts.get_parser(bufnr, nil, { error = false }) if not parser then return '0' end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 417b8bafbb..632d045293 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -177,7 +177,7 @@ function M.lint(buf, opts) is_first_lang = i == 1, } - local parser = assert(vim.treesitter._get_parser(buf), 'query parser not found.') + local parser = assert(vim.treesitter.get_parser(buf, nil, { error = false })) parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 2a6e166ef1..dbf0e73250 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -76,9 +76,14 @@ end --- ---@package function TSTreeView:new(bufnr, lang) - local parser = vim.treesitter._get_parser(bufnr or 0, lang) + local parser = vim.treesitter.get_parser(bufnr or 0, lang, { error = false }) if not parser then - return nil, 'No parser available for the given buffer.' + return nil, + string.format( + 'Failed to create TSTreeView for buffer %s: no parser for lang "%s"', + bufnr, + lang + ) end -- For each child tree (injected language), find the root of the tree and locate the node within @@ -539,7 +544,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') local function update_editor_highlights(query_win, base_win, lang) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) - local parser = assert(vim.treesitter._get_parser(base_buf, lang)) + local parser = assert(vim.treesitter.get_parser(base_buf, lang, { error = false })) api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') @@ -596,9 +601,10 @@ function M.edit_query(lang) end vim.cmd(cmd) - local parser = vim.treesitter._get_parser(buf, lang) + local parser = vim.treesitter.get_parser(buf, lang, { error = false }) if not parser then - return nil, 'No parser available for the given buffer' + return nil, + string.format('Failed to show query editor for buffer %s: no parser for lang "%s"', buf, lang) end lang = parser:lang() diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index bd0574b8b7..758c8bb356 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1028,7 +1028,11 @@ end --- --- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype. function M.edit(lang) - vim.treesitter.dev.edit_query(lang) + -- TODO(ribru17): Make edit_query return true upon success so we can just assert here + local _, err_msg = vim.treesitter.dev.edit_query(lang) + if err_msg then + error(err_msg) + end end return M -- cgit From 4349bdbd0bd0096b4f4bea0feda0961e3b03f3cc Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 27 Sep 2024 18:08:05 -0700 Subject: fix(treesitter): specify success status in edit_query return value --- runtime/lua/vim/treesitter/dev.lua | 4 ++++ runtime/lua/vim/treesitter/query.lua | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index dbf0e73250..6a2f7ae936 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -579,6 +579,8 @@ end --- @private --- @param lang? string language to open the query editor for. +--- @return boolean? `true` on success, `nil` on failure +--- @return string? error message, if applicable function M.edit_query(lang) local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() @@ -678,6 +680,8 @@ function M.edit_query(lang) }) vim.cmd('normal! G') vim.cmd.startinsert() + + return true end return M diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 758c8bb356..135250578e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1028,11 +1028,7 @@ end --- --- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype. function M.edit(lang) - -- TODO(ribru17): Make edit_query return true upon success so we can just assert here - local _, err_msg = vim.treesitter.dev.edit_query(lang) - if err_msg then - error(err_msg) - end + assert(vim.treesitter.dev.edit_query(lang)) end return M -- cgit From 041d98fe8d892a81ed659c32be5360d4f80e7d18 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 14 Sep 2024 13:27:44 +0200 Subject: feat(treesitter)!: add default fallback to `ft_to_lang` lookups Problem: Language names are only registered for filetype<->language lookups when parsers are actually loaded; this means users cannot rely on `vim.treesitter.language.get_lang()` or `get_filetypes()` to return the correct value when language and filetype coincide and always need to add explicit fallbacks. Solution: Always return the language name as valid filetype in `get_filetypes()`, and default to the filetype in `get_lang()`. Document this behavior. --- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/language.lua | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 632d045293..c5e4b86e1e 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -41,7 +41,7 @@ local function guess_query_lang(buf) local filename = api.nvim_buf_get_name(buf) if filename ~= '' then local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') - return resolved_filename and vim.treesitter.language.get_lang(resolved_filename) or nil + return resolved_filename and vim.treesitter.language.get_lang(resolved_filename) end end diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index e31ce6cb59..e4e1c2fff0 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -7,11 +7,15 @@ local ft_to_lang = { help = 'vimdoc', } ---- Get the filetypes associated with the parser named {lang}. +--- Returns the filetypes for which a parser named {lang} is used. +--- +--- The list includes {lang} itself plus all filetypes registered via +--- |vim.treesitter.language.register()|. +--- --- @param lang string Name of parser --- @return string[] filetypes function M.get_filetypes(lang) - local r = {} ---@type string[] + local r = { lang } ---@type string[] for ft, p in pairs(ft_to_lang) do if p == lang then r[#r + 1] = ft @@ -20,6 +24,12 @@ function M.get_filetypes(lang) return r end +--- Returns the language name to be used when loading a parser for {filetype}. +--- +--- If no language has been explicitly registered via |vim.treesitter.language.register()|, +--- default to {filetype}. For composite filetypes like `html.glimmer`, only the main filetype is +--- returned. +--- --- @param filetype string --- @return string|nil function M.get_lang(filetype) @@ -29,9 +39,9 @@ function M.get_lang(filetype) if ft_to_lang[filetype] then return ft_to_lang[filetype] end - -- support subfiletypes like html.glimmer + -- for subfiletypes like html.glimmer use only "main" filetype filetype = vim.split(filetype, '.', { plain = true })[1] - return ft_to_lang[filetype] + return ft_to_lang[filetype] or filetype end ---@deprecated @@ -59,10 +69,6 @@ end ---@class vim.treesitter.language.add.Opts ---@inlinedoc --- ----Default filetype the parser should be associated with. ----(Default: {lang}) ----@field filetype? string|string[] ---- ---Optional path the parser is located at ---@field path? string --- @@ -78,21 +84,18 @@ end function M.add(lang, opts) opts = opts or {} local path = opts.path - local filetype = opts.filetype or lang local symbol_name = opts.symbol_name vim.validate({ lang = { lang, 'string' }, path = { path, 'string', true }, symbol_name = { symbol_name, 'string', true }, - filetype = { filetype, { 'string', 'table' }, true }, }) -- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems) lang = lang:lower() if vim._ts_has_language(lang) then - M.register(lang, filetype) return end @@ -117,7 +120,6 @@ function M.add(lang, opts) else vim._ts_add_language_from_object(path, lang, symbol_name) end - M.register(lang, filetype) end --- @param x string|string[] -- cgit From 99e0facf3a001608287ec6db69b01c77443c7b9d Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 15 Sep 2024 14:19:08 +0200 Subject: feat(treesitter)!: use return values in `language.add()` Problem: No clear way to check whether parsers are available for a given language. Solution: Make `language.add()` return `true` if a parser was successfully added and `nil` otherwise. Use explicit `assert` instead of relying on thrown errors. --- runtime/lua/vim/treesitter/language.lua | 45 ++++++++++++++++++++--------- runtime/lua/vim/treesitter/languagetree.lua | 4 +-- runtime/lua/vim/treesitter/query.lua | 3 +- 3 files changed, 34 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index e4e1c2fff0..9f7807e036 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -62,8 +62,22 @@ function M.require_language(lang, path, silent, symbol_name) return installed end - M.add(lang, opts) - return true + return M.add(lang, opts) +end + +--- Load wasm or native parser (wrapper) +--- todo(clason): move to C +--- +---@param path string Path of parser library +---@param lang string Language name +---@param symbol_name? string Internal symbol name for the language to load (default lang) +---@return boolean? True if parser is loaded +local function loadparser(path, lang, symbol_name) + if vim.endswith(path, '.wasm') then + return vim._ts_add_language_from_wasm and vim._ts_add_language_from_wasm(path, lang) + else + return vim._ts_add_language_from_object(path, lang, symbol_name) + end end ---@class vim.treesitter.language.add.Opts @@ -77,10 +91,18 @@ end --- Load parser with name {lang} --- ---- Parsers are searched in the `parser` runtime directory, or the provided {path} +--- Parsers are searched in the `parser` runtime directory, or the provided {path}. +--- Can be used to check for available parsers before enabling treesitter features, e.g., +--- ```lua +--- if vim.treesitter.language.add('markdown') then +--- vim.treesitter.start(bufnr, 'markdown') +--- end +--- ``` --- ---@param lang string Name of the parser (alphanumerical and `_` only) ---@param opts? vim.treesitter.language.add.Opts Options: +---@return boolean? True if parser is loaded +---@return string? Error if parser cannot be loaded function M.add(lang, opts) opts = opts or {} local path = opts.path @@ -96,30 +118,25 @@ function M.add(lang, opts) lang = lang:lower() if vim._ts_has_language(lang) then - return + return true end if path == nil then + -- allow only safe language names when looking for libraries to load if not (lang and lang:match('[%w_]+') == lang) then - error("'" .. lang .. "' is not a valid language name") + return nil, string.format('Invalid language name "%s"', lang) end local fname = 'parser/' .. lang .. '.*' local paths = api.nvim_get_runtime_file(fname, false) if #paths == 0 then - error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") + return nil, string.format('No parser for language "%s"', lang) end path = paths[1] end - if vim.endswith(path, '.wasm') then - if not vim._ts_add_language_from_wasm then - error(string.format("Unable to load wasm parser '%s': not built with ENABLE_WASMTIME ", path)) - end - vim._ts_add_language_from_wasm(path, lang) - else - vim._ts_add_language_from_object(path, lang, symbol_name) - end + return loadparser(path, lang, symbol_name) or nil, + string.format('Cannot load parser %s for language "%s"', path, lang) end --- @param x string|string[] diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index cc9ffeaa29..fd68c2b910 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -108,7 +108,7 @@ LanguageTree.__index = LanguageTree ---@param opts vim.treesitter.LanguageTree.new.Opts? ---@return vim.treesitter.LanguageTree parser object function LanguageTree.new(source, lang, opts) - language.add(lang) + assert(language.add(lang)) opts = opts or {} if source == 0 then @@ -734,7 +734,7 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges) table.insert(t[tree_index][lang][pattern].regions, ranges) end --- TODO(clason): replace by refactored `ts.has_parser` API (without registering) +-- TODO(clason): replace by refactored `ts.has_parser` API (without side effects) --- The result of this function is cached to prevent nvim_get_runtime_file from being --- called too often --- @param lang string parser name diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 135250578e..4614967799 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -247,8 +247,7 @@ end) --- ---@see [vim.treesitter.query.get()] M.parse = memoize('concat-2', function(lang, query) - language.add(lang) - + assert(language.add(lang)) local ts_query = vim._ts_parse_query(lang, query) return Query.new(lang, ts_query) end) -- cgit From 5331f87f6145f705c73c5c23f365cecb9fbc5067 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Tue, 1 Oct 2024 12:07:30 -0400 Subject: fix(treesitter): indent size for inspect_tree #28727 Problem: For :InspectTree, indent size (`&shiftwidth`) for the tree viewer may be incorrect. This is because the tree viewer buffer with the filetype `query` does not explicitly configures the tab size, which can mismatch with the default indent size (2) assumed by TSTreeView's implementation. Solution: Set shiftwidth to be the same as TSTreeViewOpts specifies, which defaults to 2. --- runtime/lua/vim/treesitter/dev.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 6a2f7ae936..90c3720b80 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -158,7 +158,8 @@ end ---@param w integer ---@param b integer -local function set_dev_options(w, b) +---@param opts nil|{ indent?: integer } +local function set_dev_options(w, b, opts) vim.wo[w].scrolloff = 5 vim.wo[w].wrap = false vim.wo[w].foldmethod = 'expr' @@ -170,6 +171,11 @@ local function set_dev_options(w, b) vim.bo[b].bufhidden = 'wipe' vim.bo[b].filetype = 'query' vim.bo[b].swapfile = false + + opts = opts or {} + if opts.indent then + vim.bo[b].shiftwidth = opts.indent + end end --- Updates the cursor position in the inspector to match the node under the cursor. @@ -360,7 +366,7 @@ function M.inspect_tree(opts) vim.b[buf].dev_inspect = w vim.b[b].dev_base = win -- base window handle vim.b[b].disable_query_linter = true - set_dev_options(w, b) + set_dev_options(w, b, { indent = treeview.opts.indent }) local title --- @type string? local opts_title = opts.title -- cgit From f62728cd80a9c458b1c0ef7c5c1251e55fe91090 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 3 Oct 2024 16:57:19 -0700 Subject: docs(treesitter): generate TSNode, TSTree docs #30643 **Problem:** The documentation for `TSNode` and `TSTree` methods is incomplete from the LSP perspective. This is because they are written directly to the vimdoc, rather than in Lua and generated to vimdoc. **Solution:** Migrate the docs to Lua and generate them into the vimdoc. This requires breaking up the `treesitter/_meta.lua` file into a directory with a few different modules. This commit also makes the vimdoc generator slightly more robust with regard to sections that have multiple help tags (e.g. `*one* *two*`) --- runtime/lua/vim/treesitter/_meta.lua | 119 ------------------ runtime/lua/vim/treesitter/_meta/misc.lua | 78 ++++++++++++ runtime/lua/vim/treesitter/_meta/tsnode.lua | 185 ++++++++++++++++++++++++++++ runtime/lua/vim/treesitter/_meta/tstree.lua | 44 +++++++ 4 files changed, 307 insertions(+), 119 deletions(-) delete mode 100644 runtime/lua/vim/treesitter/_meta.lua create mode 100644 runtime/lua/vim/treesitter/_meta/misc.lua create mode 100644 runtime/lua/vim/treesitter/_meta/tsnode.lua create mode 100644 runtime/lua/vim/treesitter/_meta/tstree.lua (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua deleted file mode 100644 index 39d1f1f5f8..0000000000 --- a/runtime/lua/vim/treesitter/_meta.lua +++ /dev/null @@ -1,119 +0,0 @@ ----@meta -error('Cannot require a meta file') - ----@class TSNode: userdata ----@field id fun(self: TSNode): string ----@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer ----@field range fun(self: TSNode, include_bytes: true): integer, integer, integer, integer, integer, integer ----@field start fun(self: TSNode): integer, integer, integer ----@field end_ fun(self: TSNode): integer, integer, integer ----@field type fun(self: TSNode): string ----@field symbol fun(self: TSNode): integer ----@field named fun(self: TSNode): boolean ----@field missing fun(self: TSNode): boolean ----@field extra fun(self: TSNode): boolean ----@field child_count fun(self: TSNode): integer ----@field named_child_count fun(self: TSNode): integer ----@field child fun(self: TSNode, index: integer): TSNode? ----@field named_child fun(self: TSNode, index: integer): TSNode? ----@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode? ----@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode? ----@field parent fun(self: TSNode): TSNode? ----@field child_containing_descendant fun(self: TSNode, descendant: TSNode): TSNode? ----@field next_sibling fun(self: TSNode): TSNode? ----@field prev_sibling fun(self: TSNode): TSNode? ----@field next_named_sibling fun(self: TSNode): TSNode? ----@field prev_named_sibling fun(self: TSNode): TSNode? ----@field named_children fun(self: TSNode): TSNode[] ----@field has_changes fun(self: TSNode): boolean ----@field has_error fun(self: TSNode): boolean ----@field sexpr fun(self: TSNode): string ----@field equal fun(self: TSNode, other: TSNode): boolean ----@field iter_children fun(self: TSNode): fun(): TSNode, string ----@field field fun(self: TSNode, name: string): TSNode[] ----@field byte_length fun(self: TSNode): integer ----@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean -local TSNode = {} - ----@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) - ----@class TSParser: userdata ----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[] ----@field reset fun(self: TSParser) ----@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ----@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) ----@field set_timeout fun(self: TSParser, timeout: integer) ----@field timeout fun(self: TSParser): integer ----@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) ----@field _logger fun(self: TSParser): TSLoggerCallback - ----@class TSTree: userdata ----@field root fun(self: TSTree): TSNode ----@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) ----@field copy fun(self: TSTree): TSTree ----@field included_ranges fun(self: TSTree, include_bytes: true): Range6[] ----@field included_ranges fun(self: TSTree, include_bytes: false): Range4[] - ----@class TSQuery: userdata ----@field inspect fun(self: TSQuery): TSQueryInfo - ----@class (exact) TSQueryInfo ----@field captures string[] ----@field patterns table - ---- @param lang string ---- @return table -vim._ts_inspect_language = function(lang) end - ----@return integer -vim._ts_get_language_version = function() end - ---- @param path string ---- @param lang string ---- @param symbol_name? string -vim._ts_add_language_from_object = function(path, lang, symbol_name) end - ---- @param path string ---- @param lang string -vim._ts_add_language_from_wasm = function(path, lang) end - ----@return integer -vim._ts_get_minimum_language_version = function() end - ----@param lang string Language to use for the query ----@param query string Query string in s-expr syntax ----@return TSQuery -vim._ts_parse_query = function(lang, query) end - ----@param lang string ----@return TSParser -vim._create_ts_parser = function(lang) end - ---- @class TSQueryMatch: userdata ---- @field captures fun(self: TSQueryMatch): table -local TSQueryMatch = {} - ---- @return integer match_id ---- @return integer pattern_index -function TSQueryMatch:info() end - ---- @class TSQueryCursor: userdata ---- @field remove_match fun(self: TSQueryCursor, id: integer) -local TSQueryCursor = {} - ---- @return integer capture ---- @return TSNode captured_node ---- @return TSQueryMatch match -function TSQueryCursor:next_capture() end - ---- @return TSQueryMatch match -function TSQueryCursor:next_match() end - ---- @param node TSNode ---- @param query TSQuery ---- @param start integer? ---- @param stop integer? ---- @param opts? { max_start_depth?: integer, match_limit?: integer} ---- @return TSQueryCursor -function vim._create_ts_querycursor(node, query, start, stop, opts) end diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua new file mode 100644 index 0000000000..33701ef254 --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/misc.lua @@ -0,0 +1,78 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) + +---@class TSParser: userdata +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[] +---@field reset fun(self: TSParser) +---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] +---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) +---@field set_timeout fun(self: TSParser, timeout: integer) +---@field timeout fun(self: TSParser): integer +---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) +---@field _logger fun(self: TSParser): TSLoggerCallback + +---@class TSQuery: userdata +---@field inspect fun(self: TSQuery): TSQueryInfo + +---@class (exact) TSQueryInfo +---@field captures string[] +---@field patterns table + +--- @param lang string +--- @return table +vim._ts_inspect_language = function(lang) end + +---@return integer +vim._ts_get_language_version = function() end + +--- @param path string +--- @param lang string +--- @param symbol_name? string +vim._ts_add_language_from_object = function(path, lang, symbol_name) end + +--- @param path string +--- @param lang string +vim._ts_add_language_from_wasm = function(path, lang) end + +---@return integer +vim._ts_get_minimum_language_version = function() end + +---@param lang string Language to use for the query +---@param query string Query string in s-expr syntax +---@return TSQuery +vim._ts_parse_query = function(lang, query) end + +---@param lang string +---@return TSParser +vim._create_ts_parser = function(lang) end + +--- @class TSQueryMatch: userdata +--- @field captures fun(self: TSQueryMatch): table +local TSQueryMatch = {} -- luacheck: no unused + +--- @return integer match_id +--- @return integer pattern_index +function TSQueryMatch:info() end + +--- @class TSQueryCursor: userdata +--- @field remove_match fun(self: TSQueryCursor, id: integer) +local TSQueryCursor = {} -- luacheck: no unused + +--- @return integer capture +--- @return TSNode captured_node +--- @return TSQueryMatch match +function TSQueryCursor:next_capture() end + +--- @return TSQueryMatch match +function TSQueryCursor:next_match() end + +--- @param node TSNode +--- @param query TSQuery +--- @param start integer? +--- @param stop integer? +--- @param opts? { max_start_depth?: integer, match_limit?: integer} +--- @return TSQueryCursor +function vim._create_ts_querycursor(node, query, start, stop, opts) end diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua new file mode 100644 index 0000000000..acc9f8d24e --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -0,0 +1,185 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +--- @brief A "treesitter node" represents one specific element of the parsed contents of a buffer, +--- which can be captured by a |Query| for, e.g., highlighting. It is a |userdata| reference to an +--- object held by the treesitter library. +--- +--- An instance `TSNode` of a treesitter node supports the following methods. + +---@nodoc +---@class TSNode: userdata +---@field named_children fun(self: TSNode): TSNode[] +---@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean +local TSNode = {} -- luacheck: no unused + +--- Get the node's immediate parent. +--- Prefer |TSNode:child_containing_descendant()| +--- for iterating over the node's ancestors. +--- @return TSNode? +function TSNode:parent() end + +--- Get the node's next sibling. +--- @return TSNode? +function TSNode:next_sibling() end + +--- Get the node's previous sibling. +--- @return TSNode? +function TSNode:prev_sibling() end + +--- Get the node's next named sibling. +--- @return TSNode? +function TSNode:next_named_sibling() end + +--- Get the node's previous named sibling. +--- @return TSNode? +function TSNode:prev_named_sibling() end + +--- Iterates over all the direct children of {TSNode}, regardless of whether +--- they are named or not. +--- Returns the child node plus the eventual field name corresponding to this +--- child node. +--- @return fun(): TSNode, string +function TSNode:iter_children() end + +--- Returns a table of the nodes corresponding to the {name} field. +--- @param name string +--- @return TSNode[] +function TSNode:field(name) end + +--- Get the node's number of children. +--- @return integer +function TSNode:child_count() end + +--- Get the node's child at the given {index}, where zero represents the first +--- child. +--- @param index integer +--- @return TSNode? +function TSNode:child(index) end + +--- Get the node's number of named children. +--- @return integer +function TSNode:named_child_count() end + +--- Get the node's named child at the given {index}, where zero represents the +--- first named child. +--- @param index integer +--- @return TSNode? +function TSNode:named_child(index) end + +--- Get the node's child that contains {descendant}. +--- @param descendant TSNode +--- @return TSNode? +function TSNode:child_containing_descendant(descendant) end + +--- Get the node's start position. Return three values: the row, column and +--- total byte count (all zero-based). +--- @return integer, integer, integer +function TSNode:start() end + +--- Get the node's end position. Return three values: the row, column and +--- total byte count (all zero-based). +--- @return integer, integer, integer +function TSNode:end_() end + +--- Get the range of the node. +--- +--- Return four or six values: +--- +--- - start row +--- - start column +--- - start byte (if {include_bytes} is `true`) +--- - end row +--- - end column +--- - end byte (if {include_bytes} is `true`) +--- @param include_bytes boolean? +function TSNode:range(include_bytes) end + +--- @nodoc +--- @param include_bytes false? +--- @return integer, integer, integer, integer +function TSNode:range(include_bytes) end + +--- @nodoc +--- @param include_bytes true +--- @return integer, integer, integer, integer, integer, integer +function TSNode:range(include_bytes) end + +--- Get the node's type as a string. +--- @return string +function TSNode:type() end + +--- Get the node's type as a numerical id. +--- @return integer +function TSNode:symbol() end + +--- Check if the node is named. Named nodes correspond to named rules in the +--- grammar, whereas anonymous nodes correspond to string literals in the +--- grammar. +--- @return boolean +function TSNode:named() end + +--- Check if the node is missing. Missing nodes are inserted by the parser in +--- order to recover from certain kinds of syntax errors. +--- @return boolean +function TSNode:missing() end + +--- Check if the node is extra. Extra nodes represent things like comments, +--- which are not required by the grammar but can appear anywhere. +--- @return boolean +function TSNode:extra() end + +--- Check if a syntax node has been edited. +--- @return boolean +function TSNode:has_changes() end + +--- Check if the node is a syntax error or contains any syntax errors. +--- @return boolean +function TSNode:has_error() end + +--- Get an S-expression representing the node as a string. +--- @return string +function TSNode:sexpr() end + +--- Get a unique identifier for the node inside its own tree. +--- +--- No guarantees are made about this identifier's internal representation, +--- except for being a primitive Lua type with value equality (so not a +--- table). Presently it is a (non-printable) string. +--- +--- Note: The `id` is not guaranteed to be unique for nodes from different +--- trees. +--- @return string +function TSNode:id() end + +--- Get the |TSTree| of the node. +--- @return TSTree +function TSNode:tree() end + +--- Get the smallest node within this node that spans the given range of (row, +--- column) positions +--- @param start_row integer +--- @param start_col integer +--- @param end_row integer +--- @param end_col integer +--- @return TSNode? +function TSNode:descendant_for_range(start_row, start_col, end_row, end_col) end + +--- Get the smallest named node within this node that spans the given range of +--- (row, column) positions +--- @param start_row integer +--- @param start_col integer +--- @param end_row integer +--- @param end_col integer +--- @return TSNode? +function TSNode:named_descendant_for_range(start_row, start_col, end_row, end_col) end + +--- Check if {node} refers to the same node within the same tree. +--- @param node TSNode +--- @return boolean +function TSNode:equal(node) end + +--- Return the number of bytes spanned by this node. +--- @return integer +function TSNode:byte_length() end diff --git a/runtime/lua/vim/treesitter/_meta/tstree.lua b/runtime/lua/vim/treesitter/_meta/tstree.lua new file mode 100644 index 0000000000..24cb60040e --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/tstree.lua @@ -0,0 +1,44 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +--- @brief A "treesitter tree" represents the parsed contents of a buffer, which can be +--- used to perform further analysis. It is a |userdata| reference to an object +--- held by the treesitter library. +--- +--- An instance `TSTree` of a treesitter tree supports the following methods. + +---@nodoc +---@class TSTree: userdata +local TSTree = {} -- luacheck: no unused + +--- Return the root node of this tree. +---@return TSNode +function TSTree:root() end + +-- stylua: ignore +---@param start_byte integer +---@param end_byte_old integer +---@param end_byte_new integer +---@param start_row integer +---@param start_col integer +---@param end_row_old integer +---@param end_col_old integer +---@param end_row_new integer +---@param end_col_new integer +---@nodoc +function TSTree:edit(start_byte, end_byte_old, end_byte_new, start_row, start_col, end_row_old, end_col_old, end_row_new, end_col_new) end + +--- Returns a copy of the `TSTree`. +---@return TSTree +function TSTree:copy() end + +---@param include_bytes true +---@return Range6[] +---@nodoc +function TSTree:included_ranges(include_bytes) end + +---@param include_bytes false +---@return Range4[] +---@nodoc +function TSTree:included_ranges(include_bytes) end -- cgit