From 75adfefc85bcf0d62d2c0f51a951e6003b595cea Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Mon, 18 Jul 2022 14:21:40 +0200 Subject: feat(extmarks,ts,spell): full support for spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added 'spell' option to extmarks: Extmarks with this set will have the region spellchecked. - Added 'noplainbuffer' option to 'spelloptions': This is used to tell Neovim not to spellcheck the buffer. The old behaviour was to spell check the whole buffer unless :syntax was set. - Added spelling support to the treesitter highlighter: @spell captures in highlights.scm are used to define regions which should be spell checked. - Added support for navigating spell errors for extmarks: Works for both ephemeral and static extmarks - Added '_on_spell_nav' callback for decoration providers: Since ephemeral callbacks are only drawn for the visible screen, providers must implement this callback to instruct Neovim which regions in the buffer need can be spell checked. The callback takes a start position and an end position. Note: this callback is subject to change hence the _ prefix. - Added spell captures for built-in support languages Co-authored-by: Lewis Russell Co-authored-by: Björn Linse --- runtime/lua/vim/treesitter/highlighter.lua | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 1f242b0fdd..9e95af98db 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -97,6 +97,7 @@ function TSHighlighter.new(tree, opts) if vim.g.syntax_on ~= 1 then vim.api.nvim_command('runtime! syntax/synload.vim') end + vim.bo[self.bufnr].spelloptions = 'noplainbuffer' self.tree:parse() @@ -156,7 +157,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line) +local function on_line_impl(self, buf, line, spell) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -193,7 +194,9 @@ local function on_line_impl(self, buf, line) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - if hl and end_row >= line then + local is_spell = highlighter_query:query().captures[capture] == 'spell' + + if hl and end_row >= line and (not spell or is_spell) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, @@ -201,6 +204,7 @@ local function on_line_impl(self, buf, line) ephemeral = true, priority = tonumber(metadata.priority) or 100, -- Low but leaves room below conceal = metadata.conceal, + spell = is_spell, }) end if start_row > line then @@ -217,7 +221,21 @@ function TSHighlighter._on_line(_, _win, buf, line, _) return end - on_line_impl(self, buf, line) + on_line_impl(self, buf, line, false) +end + +---@private +function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) + local self = TSHighlighter.active[buf] + if not self then + return + end + + self:reset_highlight_state() + + for row = srow, erow do + on_line_impl(self, buf, row, true) + end end ---@private @@ -244,6 +262,7 @@ a.nvim_set_decoration_provider(ns, { on_buf = TSHighlighter._on_buf, on_win = TSHighlighter._on_win, on_line = TSHighlighter._on_line, + _on_spell_nav = TSHighlighter._on_spell_nav, }) return TSHighlighter -- cgit From 707edfc9e6a1745f268d1a9184183da521dc86b8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 6 Sep 2022 19:22:05 +0100 Subject: fix(ts): do not clobber spelloptions (#20095) --- runtime/lua/vim/treesitter/highlighter.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 9e95af98db..1e625eddb8 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -86,7 +86,7 @@ function TSHighlighter.new(tree, opts) end end - a.nvim_buf_set_option(self.bufnr, 'syntax', '') + vim.bo[self.bufnr].syntax = '' TSHighlighter.active[self.bufnr] = self @@ -95,9 +95,12 @@ function TSHighlighter.new(tree, opts) -- syntax FileType autocmds. Later on we should integrate with the -- `:syntax` and `set syntax=...` machinery properly. if vim.g.syntax_on ~= 1 then - vim.api.nvim_command('runtime! syntax/synload.vim') + vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) end - vim.bo[self.bufnr].spelloptions = 'noplainbuffer' + + a.nvim_buf_call(self.bufnr, function() + vim.opt_local.spelloptions:append('noplainbuffer') + end) self.tree:parse() -- cgit From fd1595514b747d8b083f78007579d869ccfbe89c Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Wed, 7 Sep 2022 08:39:56 +0200 Subject: Use weak tables in tree-sitter code (#17117) feat(treesitter): use weak tables when possible Also add the defaulttable function to create a table whose values are created when a key is missing. --- runtime/lua/vim/treesitter/query.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 697e2e7691..78042a74bf 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -140,12 +140,9 @@ function M.get_query(lang, query_name) end end -local query_cache = setmetatable({}, { - __index = function(tbl, key) - rawset(tbl, key, {}) - return rawget(tbl, key) - end, -}) +local query_cache = vim.defaulttable(function() + return setmetatable({}, { __mode = 'v' }) +end) --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). -- cgit From 0405594399741babd6d935d581eac2584b289f92 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Thu, 8 Sep 2022 09:47:36 +0200 Subject: feat(treesitter)!: do not merge queries by default (#20105) Problem: Treesitter queries for a given language in runtime were merged together, leading to errors if they targeted different parser versions (e.g., bundled viml queries and those shipped by nvim-treesitter). Solution: Runtime queries now work as follows: * The last query in the rtp without `; extends` in the header will be used as the base query * All queries (without a specific order) with `; extends` are concatenated with the base query BREAKING CHANGE: queries need to be updated if they are meant to extend other queries --- runtime/lua/vim/treesitter/query.lua | 37 ++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 78042a74bf..be5b24fd95 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -47,6 +47,9 @@ function M.get_query_files(lang, query_name, is_included) return {} end + local base_query = nil + local extensions = {} + local base_langs = {} -- Now get the base languages by looking at the first line of every file @@ -55,13 +58,26 @@ function M.get_query_files(lang, query_name, is_included) -- -- {language} ::= {lang} | ({lang}) local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$' + local EXTENDS_FORMAT = '^;+%s*extends%s*$' - for _, file in ipairs(lang_files) do - local modeline = safe_read(file, '*l') + for _, filename in ipairs(lang_files) do + local file, err = io.open(filename, 'r') + if not file then + error(err) + end - if modeline then - local langlist = modeline:match(MODELINE_FORMAT) + local extension = false + + for modeline in + function() + return file:read('*l') + end + do + if not vim.startswith(modeline, ';') then + break + end + local langlist = modeline:match(MODELINE_FORMAT) if langlist then for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') @@ -74,16 +90,25 @@ function M.get_query_files(lang, query_name, is_included) table.insert(base_langs, incllang) end end + elseif modeline:match(EXTENDS_FORMAT) then + extension = true end end + + if extension then + table.insert(extensions, filename) + else + base_query = filename + end + io.close(file) end - local query_files = {} + local query_files = { base_query } for _, base_lang in ipairs(base_langs) do local base_files = M.get_query_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end - vim.list_extend(query_files, lang_files) + vim.list_extend(query_files, extensions) return query_files end -- cgit From 893b659e88c61a8c3ce5b140ab475cd67e0ca6bc Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 8 Sep 2022 11:17:29 +0200 Subject: fix(treesitter): use the right loading order for base queries (#20117) Use the first, not last, query for a language on runtimepath. Typically, this implies that a user query will override a site plugin query, which will override a bundled runtime query. --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index be5b24fd95..2f6227af8e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -97,7 +97,7 @@ function M.get_query_files(lang, query_name, is_included) if extension then table.insert(extensions, filename) - else + elseif base_query == nil then base_query = filename end io.close(file) -- cgit From 1939518ebab72878f2a8ca0cb85c09f7e70d1093 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sat, 10 Sep 2022 11:26:23 +0200 Subject: fix(treesitter): prevent endless loop on self-inheritence Fixes #20139 --- runtime/lua/vim/treesitter/query.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 2f6227af8e..90ed2a357c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -34,6 +34,18 @@ local function safe_read(filename, read_quantifier) return content end +---@private +--- Adds @p ilang to @p base_langs, only if @p ilang is different than @lang +--- +---@return boolean true it lang == ilang +local function add_included_lang(base_langs, lang, ilang) + if lang == ilang then + return true + end + table.insert(base_langs, ilang) + return false +end + --- Gets the list of files used to make up a query --- ---@param lang The language @@ -84,10 +96,14 @@ function M.get_query_files(lang, query_name, is_included) if is_optional then if not is_included then - table.insert(base_langs, incllang:sub(2, #incllang - 1)) + if add_included_lang(base_langs, lang, incllang:sub(2, #incllang - 1)) then + extension = true + end end else - table.insert(base_langs, incllang) + if add_included_lang(base_langs, lang, incllang) then + extension = true + end end end elseif modeline:match(EXTENDS_FORMAT) then -- cgit From ddb762f4013ac2532ad45704466058d867e3a6ed Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 14 Sep 2022 11:08:31 +0200 Subject: docs(treesitter): clean up and update treesitter.txt (#20142) * add type annotations to code * clean up and expand static documentation * consistent use of tags for static and generated docs --- runtime/lua/vim/treesitter/health.lua | 2 +- runtime/lua/vim/treesitter/highlighter.lua | 14 ++-- runtime/lua/vim/treesitter/language.lua | 16 ++-- runtime/lua/vim/treesitter/languagetree.lua | 115 +++++++++++++++++----------- runtime/lua/vim/treesitter/query.lua | 102 ++++++++++++------------ 5 files changed, 143 insertions(+), 106 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 3bd59ca282..4995c80a02 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -3,7 +3,7 @@ local ts = vim.treesitter --- Lists the parsers currently installed --- ----@return A list of parsers +---@return string[] list of parser files function M.list_parsers() return vim.api.nvim_get_runtime_file('parser/*', true) end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 1e625eddb8..83a26aff13 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -2,6 +2,7 @@ local a = vim.api local query = require('vim.treesitter.query') -- support reload for quick experimentation +---@class TSHighlighter local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter @@ -45,9 +46,10 @@ end --- Creates a new highlighter using @param tree --- ----@param tree The language tree to use for highlighting ----@param opts Table used to configure the highlighter ---- - queries: Table to overwrite queries used by the highlighter +---@param tree LanguageTree |LanguageTree| parser object to use for highlighting +---@param opts (table|nil) Configuration of the highlighter: +--- - queries table overwrite queries used by the highlighter +---@return TSHighlighter Created highlighter object function TSHighlighter.new(tree, opts) local self = setmetatable({}, TSHighlighter) @@ -149,8 +151,10 @@ function TSHighlighter:on_changedtree(changes) end --- Gets the query used for @param lang ---- ----@param lang A language used by the highlighter. +-- +---@private +---@param lang string Language used by the highlighter. +---@return Query function TSHighlighter:get_query(lang) if not self._queries[lang] then self._queries[lang] = TSHighlighterQuery.new(lang) diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index d14b825603..c92d63b8c4 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -2,14 +2,15 @@ local a = vim.api local M = {} ---- Asserts that the provided language is installed, and optionally provide a path for the parser +--- Asserts that a parser for the language {lang} is installed. --- ---- Parsers are searched in the `parser` runtime directory. +--- Parsers are searched in the `parser` runtime directory, or the provided {path} --- ----@param lang string The language the parser should parse ----@param path string|nil Optional path the parser is located at ----@param silent boolean|nil Don't throw an error if language not found ----@param symbol_name string|nil Internal symbol name for the language to load +---@param lang string Language the parser should parse +---@param path (string|nil) Optional path the parser is located at +---@param silent (boolean|nil) Don't throw an error if language not found +---@param symbol_name (string|nil) Internal symbol name for the language to load +---@return boolean If the specified language is installed function M.require_language(lang, path, silent, symbol_name) if vim._ts_has_language(lang) then return true @@ -42,7 +43,8 @@ end --- --- Inspecting provides some useful information on the language like node names, ... --- ----@param lang The language. +---@param lang string Language +---@return table function M.inspect_language(lang) M.require_language(lang) return vim._ts_inspect_language(lang) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 70317a9f94..e9d70c4204 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -2,19 +2,35 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') +---@class LanguageTree +---@field _callbacks function[] Callback handlers +---@field _children LanguageTree[] Injected languages +---@field _injection_query table Queries defining injected languages +---@field _opts table Options +---@field _parser userdata Parser for language +---@field _regions table List of regions this tree should manage and parse +---@field _lang string Language name +---@field _regions table +---@field _source (number|string) Buffer or string to parse +---@field _trees userdata[] Reference to parsed |tstree| (one for each language) +---@field _valid boolean If the parsed tree is valid + local LanguageTree = {} LanguageTree.__index = LanguageTree ---- Represents a single treesitter parser for a language. ---- The language can contain child languages with in its range, ---- hence the tree. +--- A |LanguageTree| holds the treesitter parser for a given language {lang} used +--- to parse a buffer. As the buffer may contain injected languages, the LanguageTree +--- needs to store parsers for these child languages as well (which in turn may contain +--- child languages themselves, hence the name). --- ----@param source Can be a bufnr or a string of text to parse ----@param lang The language this tree represents ----@param opts Options table ----@param opts.injections A table of language to injection query strings. ---- This is useful for overriding the built-in runtime file ---- searching for the injection language query per language. +---@param source (number|string) Buffer or a string of text to parse +---@param lang string Root language this tree represents +---@param opts (table|nil) Optional keyword arguments: +--- - injections table Mapping language to injection query strings. +--- This is useful for overriding the built-in +--- runtime file searching for the injection language +--- query per language. +---@return LanguageTree |LanguageTree| parser object function LanguageTree.new(source, lang, opts) language.require_language(lang) opts = opts or {} @@ -94,6 +110,9 @@ end --- for the language this tree represents. --- This will run the injection query for this language to --- determine if any child languages should be created. +--- +---@return userdata[] Table of parsed |tstree| +---@return table Change list function LanguageTree:parse() if self._valid then return self._trees @@ -167,10 +186,10 @@ function LanguageTree:parse() return self._trees, changes end ---- Invokes the callback for each LanguageTree and it's children recursively +--- Invokes the callback for each |LanguageTree| and its children recursively --- ----@param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string) ----@param include_self Whether to include the invoking tree in the results. +---@param fn function(tree: LanguageTree, lang: string) +---@param include_self boolean Whether to include the invoking tree in the results function LanguageTree:for_each_child(fn, include_self) if include_self then fn(self, self._lang) @@ -181,12 +200,11 @@ function LanguageTree:for_each_child(fn, include_self) end end ---- Invokes the callback for each treesitter trees recursively. +--- Invokes the callback for each |LanguageTree| recursively. --- ---- Note, this includes the invoking language tree's trees as well. +--- Note: This includes the invoking tree's child trees as well. --- ----@param fn The callback to invoke. The callback is invoked with arguments ---- (tree: TSTree, languageTree: LanguageTree) +---@param fn function(tree: TSTree, languageTree: LanguageTree) function LanguageTree:for_each_tree(fn) for _, tree in ipairs(self._trees) do fn(tree, self) @@ -197,11 +215,13 @@ function LanguageTree:for_each_tree(fn) end end ---- Adds a child language to this tree. +--- Adds a child language to this |LanguageTree|. --- --- If the language already exists as a child, it will first be removed. --- ----@param lang The language to add. +---@private +---@param lang string Language to add. +---@return LanguageTree Injected |LanguageTree| function LanguageTree:add_child(lang) if self._children[lang] then self:remove_child(lang) @@ -215,9 +235,10 @@ function LanguageTree:add_child(lang) return self._children[lang] end ---- Removes a child language from this tree. +--- Removes a child language from this |LanguageTree|. --- ----@param lang The language to remove. +---@private +---@param lang string Language to remove. function LanguageTree:remove_child(lang) local child = self._children[lang] @@ -229,12 +250,11 @@ function LanguageTree:remove_child(lang) end end ---- Destroys this language tree and all its children. +--- Destroys this |LanguageTree| and all its children. --- --- Any cleanup logic should be performed here. --- ---- Note: ---- This DOES NOT remove this tree from a parent. Instead, +--- Note: This DOES NOT remove this tree from a parent. Instead, --- `remove_child` must be called on the parent to remove it. function LanguageTree:destroy() -- Cleanup here @@ -243,23 +263,24 @@ function LanguageTree:destroy() end end ---- Sets the included regions that should be parsed by this parser. +--- Sets the included regions that should be parsed by this |LanguageTree|. --- A region is a set of nodes and/or ranges that will be parsed in the same context. --- ---- For example, `{ { node1 }, { node2} }` is two separate regions. ---- This will be parsed by the parser in two different contexts... thus resulting +--- For example, `{ { node1 }, { node2} }` contains two separate regions. +--- They will be parsed by the parser in two different contexts, thus resulting --- in two separate trees. --- ---- `{ { node1, node2 } }` is a single region consisting of two nodes. ---- This will be parsed by the parser in a single context... thus resulting +--- On the other hand, `{ { node1, node2 } }` is a single region consisting of +--- two nodes. This will be parsed by the parser in a single context, thus resulting --- in a single tree. --- --- This allows for embedded languages to be parsed together across different --- nodes, which is useful for templating languages like ERB and EJS. --- ---- Note, this call invalidates the tree and requires it to be parsed again. +--- Note: This call invalidates the tree and requires it to be parsed again. --- ----@param regions (table) list of regions this tree should manage and parse. +---@private +---@param regions table List of regions this tree should manage and parse. function LanguageTree:set_included_regions(regions) -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(regions) do @@ -288,7 +309,7 @@ function LanguageTree:set_included_regions(regions) -- Trees are no longer valid now that we have changed regions. -- TODO(vigoux,steelsojka): Look into doing this smarter so we can use some of the -- old trees for incremental parsing. Currently, this only - -- effects injected languages. + -- affects injected languages. self._trees = {} self:invalidate() end @@ -493,8 +514,8 @@ function LanguageTree:_on_detach(...) self:_do_callback('detach', ...) end ---- Registers callbacks for the parser. ----@param cbs table An |nvim_buf_attach()|-like table argument with the following keys : +--- Registers callbacks for the |LanguageTree|. +---@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers: --- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback. --- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes. --- It will only be passed one argument, which is a table of the ranges (as node ranges) that @@ -536,9 +557,10 @@ local function tree_contains(tree, range) return start_fits and end_fits end ---- Determines whether {range} is contained in this language tree +--- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range A range, that is a `{ start_line, start_col, end_line, end_col }` table. +---@param range table `{ start_line, start_col, end_line, end_col }` +---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do if tree_contains(tree, range) then @@ -549,11 +571,12 @@ function LanguageTree:contains(range) return false end ---- Gets the tree that contains {range} +--- Gets the tree that contains {range}. --- ----@param range table A text range ----@param opts table Options table ----@param opts.ignore_injections boolean (default true) Ignore injected languages. +---@param range table `{ start_line, start_col, end_line, end_col }` +---@param opts table|nil Optional keyword arguments: +--- - ignore_injections boolean Ignore injected languages (default true) +---@return userdata|nil Contained |tstree| function LanguageTree:tree_for_range(range, opts) opts = opts or {} local ignore = vim.F.if_nil(opts.ignore_injections, true) @@ -577,19 +600,21 @@ function LanguageTree:tree_for_range(range, opts) return nil end ---- Gets the smallest named node that contains {range} +--- Gets the smallest named node that contains {range}. --- ----@param range table A text range ----@param opts table Options table ----@param opts.ignore_injections boolean (default true) Ignore injected languages. +---@param range table `{ start_line, start_col, end_line, end_col }` +---@param opts table|nil Optional keyword arguments: +--- - ignore_injections boolean Ignore injected languages (default true) +---@return userdata|nil Found |tsnode| function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) return tree:root():named_descendant_for_range(unpack(range)) end ---- Gets the appropriate language that contains {range} +--- Gets the appropriate language that contains {range}. --- ----@param range A text range, see |LanguageTree:contains| +---@param range table `{ start_line, start_col, end_line, end_col }` +---@return LanguageTree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do if child:contains(range) then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 90ed2a357c..d1dc29969b 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -3,6 +3,11 @@ local language = require('vim.treesitter.language') -- query: pattern matching on trees -- predicate matching is implemented in lua +-- +---@class Query +---@field captures string[] List of captures used in query +---@field info table Contains used queries, predicates, directives +---@field query userdata Parsed query local Query = {} Query.__index = Query @@ -35,9 +40,9 @@ local function safe_read(filename, read_quantifier) end ---@private ---- Adds @p ilang to @p base_langs, only if @p ilang is different than @lang +--- Adds {ilang} to {base_langs}, only if {ilang} is different than {lang} --- ----@return boolean true it lang == ilang +---@return boolean true If lang == ilang local function add_included_lang(base_langs, lang, ilang) if lang == ilang then return true @@ -48,9 +53,10 @@ end --- Gets the list of files used to make up a query --- ----@param lang The language ----@param query_name The name of the query to load ----@param is_included Internal parameter, most of the time left as `nil` +---@param lang string Language to get query for +---@param query_name string Name of the query to load (e.g., 'highlights') +---@param is_included (boolean|nil) Internal parameter, most of the time left as `nil` +---@return string[] query_files List of files to load for given query and language function M.get_query_files(lang, query_name, is_included) local query_path = string.format('queries/%s/%s.scm', lang, query_name) local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) @@ -150,24 +156,24 @@ local explicit_queries = setmetatable({}, { end, }) ---- Sets the runtime query {query_name} for {lang} +--- Sets the runtime query named {query_name} for {lang} --- --- This allows users to override any runtime files and/or configuration --- set by plugins. --- ----@param lang string: The language to use for the query ----@param query_name string: The name of the query (i.e. "highlights") ----@param text string: The query text (unparsed). +---@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_query(lang, query_name, text) explicit_queries[lang][query_name] = M.parse_query(lang, text) end --- Returns the runtime query {query_name} for {lang}. --- ----@param lang The language to use for the query ----@param query_name The name of the query (i.e. "highlights") +---@param lang string Language to use for the query +---@param query_name string Name of the query (e.g. 'highlights') --- ----@return The corresponding query, parsed. +---@return Query Parsed query function M.get_query(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -198,10 +204,10 @@ end) --- -` info.captures` also points to `captures`. --- - `info.patterns` contains information about predicates. --- ----@param lang string The language ----@param query string A string containing the query (s-expr syntax) +---@param lang string Language to use for the query +---@param query string Query in s-expr syntax --- ----@returns The query +---@return Query Parsed query function M.parse_query(lang, query) language.require_language(lang) local cached = query_cache[lang][query] @@ -219,10 +225,11 @@ end --- Gets the text corresponding to a given node --- ----@param node table The node ----@param source table The buffer or string from which the node is extracted ----@param opts table Optional parameters. ---- - concat: (boolean default true) Concatenate result in a string +---@param node userdata |tsnode| +---@param source (number|string) Buffer or string from which the {node} is extracted +---@param opts (table|nil) Optional parameters. +--- - concat: (boolean) Concatenate result in a string (default true) +---@return (string[]|string) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) @@ -410,9 +417,8 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ----@param name the name of the predicate, without leading # ----@param handler the handler function to be used ---- signature will be (match, pattern, bufnr, predicate) +---@param name string Name of the predicate, without leading # +---@param handler function(match:string, pattern:string, bufnr:number, predicate:function) function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -428,9 +434,8 @@ end --- can set node level data by using the capture id on the --- metadata table `metadata[capture_id].key = value` --- ----@param name the name of the directive, without leading # ----@param handler the handler function to be used ---- signature will be (match, pattern, bufnr, predicate, metadata) +---@param name string Name of the directive, without leading # +---@param handler function(match:string, pattern:string, bufnr:number, predicate:function, metadata:table) function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -440,12 +445,13 @@ function M.add_directive(name, handler, force) end --- Lists the currently available directives to use in queries. ----@return The list of supported directives. +---@return string[] List of supported directives. function M.list_directives() return vim.tbl_keys(directive_handlers) end ----@return The list of supported predicates. +--- Lists the currently available predicates to use in queries. +---@return string[] List of supported predicates. function M.list_predicates() return vim.tbl_keys(predicate_handlers) end @@ -532,17 +538,16 @@ end --- Iterate over all captures from all matches inside {node} --- ---- {source} is needed if the query contains predicates, then the caller +--- {source} is needed if the query contains predicates; then the caller --- must ensure to use a freshly parsed tree consistent with the current --- text of the buffer (if relevant). {start_row} and {end_row} can be used to limit --- matches inside a row range (this is typically used with root node ---- as the node, i e to get syntax highlight matches in the current ---- viewport). When omitted the start and end row values are used from the given node. +--- as the {node}, i.e., to get syntax highlight matches in the current +--- viewport). When omitted, the {start} and {end} row values are used from the given node. --- ---- The iterator returns three values, a numeric id identifying the capture, +--- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. --- The following example shows how to get captures by name: ---- ---
 --- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
 ---   local name = query.captures[id] -- name of the capture in the query
@@ -553,13 +558,14 @@ end
 --- end
 --- 
--- ----@param node The node under which the search will occur ----@param source The source buffer or string to extract text from ----@param start The starting line of the search ----@param stop The stopping line of the search (end-exclusive) +---@param node userdata |tsnode| under which the search will occur +---@param source (number|string) Source buffer or string to extract text from +---@param start number Starting line for the search +---@param stop number Stopping line for the search (end-exclusive) --- ----@returns The matching capture id ----@returns The captured node +---@return number capture Matching capture id +---@return table capture_node Capture for {node} +---@return table metadata for the {capture} function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -589,14 +595,13 @@ end --- Iterates the matches of self on a given range. --- ---- Iterate over all matches within a node. The arguments are the same as +--- Iterate over all matches within a {node}. The arguments are the same as --- for |query:iter_captures()| but the iterated values are different: --- an (1-based) index of the pattern in the query, a table mapping --- capture indices to nodes, and metadata from any directives processing the match. ---- If the query has more than one pattern the capture table might be sparse, +--- If the query has more than one pattern, the capture table might be sparse --- and e.g. `pairs()` method should be used over `ipairs`. ---- Here an example iterating over all captures in every match: ---- +--- Here is an example iterating over all captures in every match: ---
 --- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
 ---   for id, node in pairs(match) do
@@ -610,13 +615,14 @@ end
 --- end
 --- 
--- ----@param node The node under which the search will occur ----@param source The source buffer or string to search ----@param start The starting line of the search ----@param stop The stopping line of the search (end-exclusive) +---@param node userdata |tsnode| under which the search will occur +---@param source (number|string) Source buffer or string to search +---@param start number Starting line for the search +---@param stop number Stopping line for the search (end-exclusive) --- ----@returns The matching pattern id ----@returns The matching match +---@return number pattern id +---@return table match +---@return table metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() -- cgit From 63be7651829f8b77c4974d08ebe09f7775e41a8a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 25 Sep 2022 19:58:27 -0400 Subject: fix(docs): invalid :help links #20345 Fix those naughty single quotes. closes #20159 --- runtime/lua/vim/treesitter/query.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index d1dc29969b..1e46daaccf 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -54,7 +54,7 @@ end --- Gets the list of files used to make up a query --- ---@param lang string Language to get query for ----@param query_name string Name of the query to load (e.g., 'highlights') +---@param query_name string Name of the query to load (e.g., "highlights") ---@param is_included (boolean|nil) Internal parameter, most of the time left as `nil` ---@return string[] query_files List of files to load for given query and language function M.get_query_files(lang, query_name, is_included) @@ -162,7 +162,7 @@ local explicit_queries = setmetatable({}, { --- set by plugins. --- ---@param lang string Language to use for the query ----@param query_name string Name of the query (e.g., 'highlights') +---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). function M.set_query(lang, query_name, text) explicit_queries[lang][query_name] = M.parse_query(lang, text) @@ -171,7 +171,7 @@ end --- Returns the runtime query {query_name} for {lang}. --- ---@param lang string Language to use for the query ----@param query_name string Name of the query (e.g. 'highlights') +---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query Parsed query function M.get_query(lang, query_name) @@ -596,7 +596,7 @@ end --- Iterates the matches of self on a given range. --- --- Iterate over all matches within a {node}. The arguments are the same as ---- for |query:iter_captures()| but the iterated values are different: +--- for |Query:iter_captures()| but the iterated values are different: --- an (1-based) index of the pattern in the query, a table mapping --- capture indices to nodes, and metadata from any directives processing the match. --- If the query has more than one pattern, the capture table might be sparse -- cgit From 94718e479da786cf75f9805470a6a108e8097dab Mon Sep 17 00:00:00 2001 From: Sergey Berezhnoy Date: Thu, 29 Sep 2022 17:25:07 +0300 Subject: fix(query): fix unnatural order for inherits in treesitter queries (#20298) close #20297 --- runtime/lua/vim/treesitter/query.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 1e46daaccf..7ca7384a88 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -125,11 +125,12 @@ function M.get_query_files(lang, query_name, is_included) io.close(file) end - local query_files = { base_query } + local query_files = {} for _, base_lang in ipairs(base_langs) do local base_files = M.get_query_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end + vim.list_extend(query_files, { base_query }) vim.list_extend(query_files, extensions) return query_files -- cgit From c7a88a470fa78b9e8a925b874752df473e90f6c3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 30 Oct 2022 04:44:14 +0100 Subject: feat(checkhealth): improve treesitter report --- runtime/lua/vim/treesitter/health.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 4995c80a02..1c9b58c1a8 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -1,5 +1,6 @@ local M = {} local ts = vim.treesitter +local health = require('vim.health') --- Lists the parsers currently installed --- @@ -10,27 +11,21 @@ end --- Performs a healthcheck for treesitter integration function M.check() - local report_info = vim.fn['health#report_info'] - local report_ok = vim.fn['health#report_ok'] - local report_error = vim.fn['health#report_error'] local parsers = M.list_parsers() - report_info(string.format('Runtime ABI version : %d', ts.language_version)) + health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version)) for _, parser in pairs(parsers) do local parsername = vim.fn.fnamemodify(parser, ':t:r') - local is_loadable, ret = pcall(ts.language.require_language, parsername) - if not is_loadable then - report_error(string.format('Impossible to load parser for %s: %s', parsername, ret)) + if not is_loadable or not ret then + health.report_error(string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?')) elseif ret then local lang = ts.language.inspect_language(parsername) - report_ok( - string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version) + health.report_ok( + string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) - else - report_error(string.format('Unable to load parser for %s', parsername)) end end end -- cgit From cc7c378bf319d62491ca121ab598397428e4ced4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 30 Oct 2022 06:41:28 +0100 Subject: feat(checkhealth): check runtime ($VIMRUNTIME) Move man/health.lua into the "runtime" check. fix #20696 --- runtime/lua/vim/treesitter/health.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 1c9b58c1a8..c0a1eca0ce 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -20,7 +20,9 @@ function M.check() local is_loadable, ret = pcall(ts.language.require_language, parsername) if not is_loadable or not ret then - health.report_error(string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?')) + health.report_error( + string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?') + ) elseif ret then local lang = ts.language.inspect_language(parsername) health.report_ok( -- cgit From 07eb4263caa671de63186e9bbd650ec4b1fbce1a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 28 Oct 2022 13:09:22 +0100 Subject: feat(spell): support nospell in treesitter queries --- runtime/lua/vim/treesitter/highlighter.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 83a26aff13..f5e5ca1988 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -164,7 +164,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line, spell) +local function on_line_impl(self, buf, line, is_spell_nav) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -201,17 +201,26 @@ local function on_line_impl(self, buf, line, spell) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - local is_spell = highlighter_query:query().captures[capture] == 'spell' + local capture_name = highlighter_query:query().captures[capture] + local spell = nil + if capture_name == 'spell' then + spell = true + elseif capture_name == 'nospell' then + spell = false + end + + -- Give nospell a higher priority so it always overrides spell captures. + local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - if hl and end_row >= line and (not spell or is_spell) then + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl, ephemeral = true, - priority = tonumber(metadata.priority) or 100, -- Low but leaves room below + priority = (tonumber(metadata.priority) or 100) + spell_pri_offset, -- Low but leaves room below conceal = metadata.conceal, - spell = is_spell, + spell = spell, }) end if start_row > line then -- cgit From 0b05bd87c04f9cde5c84a062453619349e370795 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 23 Nov 2022 12:31:49 +0100 Subject: docs(gen): support language annotation in docstrings --- runtime/lua/vim/treesitter/query.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7ca7384a88..4bec5db527 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -549,7 +549,7 @@ end --- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. --- The following example shows how to get captures by name: ----
+--- 
lua
 --- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
 ---   local name = query.captures[id] -- name of the capture in the query
 ---   -- typically useful info about the node:
@@ -603,7 +603,7 @@ end
 --- If the query has more than one pattern, the capture table might be sparse
 --- and e.g. `pairs()` method should be used over `ipairs`.
 --- Here is an example iterating over all captures in every match:
---- 
+--- 
lua
 --- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
 ---   for id, node in pairs(match) do
 ---     local name = query.captures[id]
-- 
cgit 


From d44699800cd0dbf14fb45476c13b6cc3c993b5c7 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Thu, 8 Dec 2022 09:22:57 -0700
Subject: feat(treesitter): add vim.treesitter.show_tree() (#21322)

Add a "show_tree" function to view a textual representation of the
nodes in a language tree in a window. Moving the cursor in the
window highlights the corresponding text in the source buffer, and
moving the cursor in the source buffer highlights the corresponding
nodes in the window.
---
 runtime/lua/vim/treesitter/languagetree.lua |   4 +-
 runtime/lua/vim/treesitter/playground.lua   | 184 ++++++++++++++++++++++++++++
 2 files changed, 187 insertions(+), 1 deletion(-)
 create mode 100644 runtime/lua/vim/treesitter/playground.lua

(limited to 'runtime/lua/vim/treesitter')

diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index e9d70c4204..a1e96f8ef2 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -608,7 +608,9 @@ end
 ---@return userdata|nil Found |tsnode|
 function LanguageTree:named_node_for_range(range, opts)
   local tree = self:tree_for_range(range, opts)
-  return tree:root():named_descendant_for_range(unpack(range))
+  if tree then
+    return tree:root():named_descendant_for_range(unpack(range))
+  end
 end
 
 --- Gets the appropriate language that contains {range}.
diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
new file mode 100644
index 0000000000..325d303df5
--- /dev/null
+++ b/runtime/lua/vim/treesitter/playground.lua
@@ -0,0 +1,184 @@
+local api = vim.api
+
+local M = {}
+
+---@class Playground
+---@field opts table Options table with the following keys:
+---                  - anon (boolean): If true, display anonymous nodes
+---                  - lang (boolean): If true, display the language alongside each node
+---
+---@class Node
+---@field id number Node id
+---@field text string Node text
+---@field named boolean True if this is a named (non-anonymous) node
+---@field depth number Depth of the node within the tree
+---@field lnum number Beginning line number of this node in the source buffer
+---@field col number Beginning column number of this node in the source buffer
+---@field end_lnum number Final line number of this node in the source buffer
+---@field end_col number Final column number of this node in the source buffer
+---@field lang string Source language of this node
+
+--- Traverse all child nodes starting at {node}.
+---
+--- This is a recursive function. The {depth} parameter indicates the current recursion level.
+--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed
+--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order
+--- they were visited.
+---
+--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each
+--- injected language has a series of trees nested within the primary language's tree, and the root
+--- node of each of these trees is contained within a node in the primary tree. The {injections}
+--- table maps nodes in the primary tree to root nodes of injected trees.
+---
+---@param node userdata Starting node to begin traversal |tsnode|
+---@param depth number Current recursion depth
+---@param lang string Language of the tree currently being traversed
+---@param injections table Mapping of node ids to root nodes of injected language trees (see
+---                        explanation above)
+---@param tree Node[] Output table containing a list of tables each representing a node in the tree
+---@private
+local function traverse(node, depth, lang, injections, tree)
+  local injection = injections[node:id()]
+  if injection then
+    traverse(injection.root, depth, injection.lang, injections, tree)
+  end
+
+  for child, field in node:iter_children() do
+    local type = child:type()
+    local lnum, col, end_lnum, end_col = child:range()
+    local named = child:named()
+    local text
+    if named then
+      if field then
+        text = string.format('%s: (%s)', field, type)
+      else
+        text = string.format('(%s)', type)
+      end
+    else
+      text = string.format('"%s"', type:gsub('\n', '\\n'))
+    end
+
+    table.insert(tree, {
+      id = child:id(),
+      text = text,
+      named = named,
+      depth = depth,
+      lnum = lnum,
+      col = col,
+      end_lnum = end_lnum,
+      end_col = end_col,
+      lang = lang,
+    })
+
+    traverse(child, depth + 1, lang, injections, tree)
+  end
+
+  return tree
+end
+
+--- Create a new Playground object.
+---
+---@param bufnr number Source buffer number
+---
+---@return Playground|nil
+---@return string|nil Error message, if any
+---
+---@private
+function M.new(self, bufnr)
+  local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0)
+  if not ok 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
+  -- the primary tree that contains that root. Add a mapping from the node in the primary tree to
+  -- the root in the child tree to the {injections} table.
+  local root = parser:parse()[1]:root()
+  local injections = {}
+  parser:for_each_child(function(child, lang)
+    child:for_each_tree(function(tree)
+      local r = tree:root()
+      local node = root:named_descendant_for_range(r:range())
+      if node then
+        injections[node:id()] = {
+          lang = lang,
+          root = r,
+        }
+      end
+    end)
+  end)
+
+  local nodes = traverse(root, 0, parser:lang(), injections, {})
+
+  local named = {}
+  for _, v in ipairs(nodes) do
+    if v.named then
+      named[#named + 1] = v
+    end
+  end
+
+  local t = {
+    ns = api.nvim_create_namespace(''),
+    nodes = nodes,
+    named = named,
+    opts = {
+      anon = false,
+      lang = false,
+    },
+  }
+
+  setmetatable(t, self)
+  self.__index = self
+  return t
+end
+
+--- Write the contents of this Playground into {bufnr}.
+---
+---@param bufnr number Buffer number to write into.
+---@private
+function M.draw(self, bufnr)
+  vim.bo[bufnr].modifiable = true
+  local lines = {}
+  for _, item in self:iter() do
+    lines[#lines + 1] = table.concat({
+      string.rep(' ', item.depth),
+      item.text,
+      item.lnum == item.end_lnum
+          and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col)
+        or string.format(
+          ' [%d:%d-%d:%d]',
+          item.lnum + 1,
+          item.col + 1,
+          item.end_lnum + 1,
+          item.end_col
+        ),
+      self.opts.lang and string.format(' %s', item.lang) or '',
+    })
+  end
+  api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
+  vim.bo[bufnr].modifiable = false
+end
+
+--- Get node {i} from this Playground object.
+---
+--- The node number is dependent on whether or not anonymous nodes are displayed.
+---
+---@param i number Node number to get
+---@return Node
+---@private
+function M.get(self, i)
+  local t = self.opts.anon and self.nodes or self.named
+  return t[i]
+end
+
+--- Iterate over all of the nodes in this Playground object.
+---
+---@return function Iterator over all nodes in this Playground
+---@return table
+---@return number
+---@private
+function M.iter(self)
+  return ipairs(self.opts.anon and self.nodes or self.named)
+end
+
+return M
-- 
cgit 


From 42009ac7df88bfffeea49a83e642fdc6cf9f9447 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Thu, 8 Dec 2022 09:51:46 -0700
Subject: feat(treesitter): add 'lang' option to show_tree() (#21341)

This is necessary for now to support filetypes that use a parser with a
different name (e.g. the "terraform" filetype uses the "hcl" parser).
---
 runtime/lua/vim/treesitter/playground.lua | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim/treesitter')

diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
index 325d303df5..bb073290c6 100644
--- a/runtime/lua/vim/treesitter/playground.lua
+++ b/runtime/lua/vim/treesitter/playground.lua
@@ -3,6 +3,7 @@ local api = vim.api
 local M = {}
 
 ---@class Playground
+---@field ns number API namespace
 ---@field opts table Options table with the following keys:
 ---                  - anon (boolean): If true, display anonymous nodes
 ---                  - lang (boolean): If true, display the language alongside each node
@@ -79,13 +80,14 @@ end
 --- Create a new Playground object.
 ---
 ---@param bufnr number Source buffer number
+---@param lang string|nil Language of source buffer
 ---
 ---@return Playground|nil
 ---@return string|nil Error message, if any
 ---
 ---@private
-function M.new(self, bufnr)
-  local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0)
+function M.new(self, bufnr, lang)
+  local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
   if not ok then
     return nil, 'No parser available for the given buffer'
   end
@@ -95,13 +97,13 @@ function M.new(self, bufnr)
   -- the root in the child tree to the {injections} table.
   local root = parser:parse()[1]:root()
   local injections = {}
-  parser:for_each_child(function(child, lang)
+  parser:for_each_child(function(child, lang_)
     child:for_each_tree(function(tree)
       local r = tree:root()
       local node = root:named_descendant_for_range(r:range())
       if node then
         injections[node:id()] = {
-          lang = lang,
+          lang = lang_,
           root = r,
         }
       end
-- 
cgit 


From d127c684faa9a112575798a81c87babb1c83a7ea Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Wed, 14 Dec 2022 15:00:48 +0000
Subject: fix(treesitter): properly restore `'syntax'` (#21358)

---
 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 f5e5ca1988..99aedf60c5 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -88,7 +88,9 @@ function TSHighlighter.new(tree, opts)
     end
   end
 
+  self.orig_syntax = vim.bo[self.bufnr].syntax
   vim.bo[self.bufnr].syntax = ''
+  vim.b[self.bufnr].ts_highlight = true
 
   TSHighlighter.active[self.bufnr] = self
 
@@ -114,6 +116,8 @@ function TSHighlighter:destroy()
   if TSHighlighter.active[self.bufnr] then
     TSHighlighter.active[self.bufnr] = nil
   end
+
+  vim.bo[self.bufnr].syntax = self.orig_syntax
 end
 
 ---@private
-- 
cgit 


From 0887ad1cbb050d2bc6169ad46aa07cf42c90493f Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Wed, 14 Dec 2022 22:54:58 +0000
Subject: fix(ts): check buffer is loaded when restoring options (#21419)

fix(treesitter): check buffer is loaded when restoring options

Also restore spelloptions

Fixes #21416
---
 runtime/lua/vim/treesitter/highlighter.lua | 7 ++++++-
 1 file changed, 6 insertions(+), 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 99aedf60c5..e99994c8a9 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -89,6 +89,8 @@ function TSHighlighter.new(tree, opts)
   end
 
   self.orig_syntax = vim.bo[self.bufnr].syntax
+  self.orig_spelloptions = vim.bo[self.bufnr].spelloptions
+
   vim.bo[self.bufnr].syntax = ''
   vim.b[self.bufnr].ts_highlight = true
 
@@ -117,7 +119,10 @@ function TSHighlighter:destroy()
     TSHighlighter.active[self.bufnr] = nil
   end
 
-  vim.bo[self.bufnr].syntax = self.orig_syntax
+  if vim.api.nvim_buf_is_loaded(self.bufnr) then
+    vim.bo[self.bufnr].syntax = self.orig_syntax
+    vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
+  end
 end
 
 ---@private
-- 
cgit