From 9a5678463c96baf3b39cb3083ddf0da87d39aa23 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 4 Feb 2023 14:58:38 +0000 Subject: fix(treesitter): fix most diagnostics --- runtime/lua/vim/treesitter/query.lua | 109 +++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 31 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index dbf134573d..84ed2667b9 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,21 +1,25 @@ local a = vim.api 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 info TSQueryInfo Contains used queries, predicates, directives ---@field query userdata Parsed query local Query = {} Query.__index = Query +---@class TSQueryInfo +---@field captures table +---@field patterns table + local M = {} ---@private +---@param files string[] +---@return string[] local function dedupe_files(files) local result = {} + ---@type table local seen = {} for _, path in ipairs(files) do @@ -65,10 +69,10 @@ function M.get_query_files(lang, query_name, is_included) return {} end - local base_query = nil + local base_query = nil ---@type string? local extensions = {} - local base_langs = {} + local base_langs = {} ---@type string[] -- Now get the base languages by looking at the first line of every file -- The syntax is the following : @@ -87,6 +91,7 @@ function M.get_query_files(lang, query_name, is_included) local extension = false for modeline in + ---@return string function() return file:read('*l') end @@ -97,6 +102,7 @@ function M.get_query_files(lang, query_name, is_included) local langlist = modeline:match(MODELINE_FORMAT) if langlist then + ---@diagnostic disable-next-line:param-type-mismatch for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') @@ -137,6 +143,8 @@ function M.get_query_files(lang, query_name, is_included) end ---@private +---@param filenames string[] +---@return string local function read_query_files(filenames) local contents = {} @@ -147,7 +155,8 @@ local function read_query_files(filenames) return table.concat(contents, '') end ---- The explicitly set queries from |vim.treesitter.query.set_query()| +-- The explicitly set queries from |vim.treesitter.query.set_query()| +---@type table> local explicit_queries = setmetatable({}, { __index = function(t, k) local lang_queries = {} @@ -174,7 +183,7 @@ end ---@param lang string Language to use for the query ---@param query_name string Name of the query (e.g. "highlights") --- ----@return Query Parsed query +---@return Query|nil Parsed query function M.get_query(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -188,6 +197,7 @@ function M.get_query(lang, query_name) end end +---@type {[string]: {[string]: Query}} local query_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) @@ -226,11 +236,11 @@ end --- Gets the text corresponding to a given node --- ----@param node userdata |tsnode| +---@param node 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) +---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) @@ -239,12 +249,12 @@ function M.get_node_text(node, source, opts) local end_row, end_col, end_byte = node:end_() if type(source) == 'number' then - local lines local eof_row = a.nvim_buf_line_count(source) if start_row >= eof_row then return nil end + local lines ---@type string[] if end_col == 0 then lines = a.nvim_buf_get_lines(source, start_row, end_row, true) end_col = -1 @@ -267,8 +277,13 @@ function M.get_node_text(node, source, opts) end end +---@alias TSMatch table + +---@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean + -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) +---@type table local predicate_handlers = { ['eq?'] = function(match, _, source, predicate) local node = match[predicate[2]] @@ -277,13 +292,13 @@ local predicate_handlers = { end local node_text = M.get_node_text(node, source) - local str + local str ---@type string if type(predicate[3]) == 'string' then -- (#eq? @aa "foo") str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) + str = M.get_node_text(match[predicate[3]], source) --[[@as string]] end if node_text ~= str or str == nil then @@ -299,7 +314,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source), regex) + return string.find(M.get_node_text(node, source) --[[@as string]], regex) ~= nil end, ['match?'] = (function() @@ -321,10 +336,12 @@ local predicate_handlers = { }) return function(match, _, source, pred) + ---@cast match TSMatch local node = match[pred[2]] if not node then return true end + ---@diagnostic disable-next-line no-unknown local regex = compiled_vim_regexes[pred[3]] return regex:match_str(M.get_node_text(node, source)) end @@ -335,7 +352,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = M.get_node_text(node, source) --[[@as string]] for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -359,6 +376,7 @@ local predicate_handlers = { if not string_set then string_set = {} for i = 3, #predicate do + ---@diagnostic disable-next-line:no-unknown string_set[predicate[i]] = true end predicate['string_set'] = string_set @@ -371,21 +389,39 @@ local predicate_handlers = { -- As we provide lua-match? also expose vim-match? predicate_handlers['vim-match?'] = predicate_handlers['match?'] +---@class TSMetadata +---@field [integer] TSMetadata +---@field [string] integer|string +---@field range Range + +---@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) + +-- Predicate handler receive the following arguments +-- (match, pattern, bufnr, predicate) + -- Directives store metadata or perform side effects against a match. -- Directives should always end with a `!`. -- Directive handler receive the following arguments -- (match, pattern, bufnr, predicate, metadata) +---@type table local directive_handlers = { ['set!'] = function(_, _, _, pred, metadata) if #pred == 4 then -- (#set! @capture "key" "value") + ---@diagnostic disable-next-line:no-unknown local _, capture_id, key, value = unpack(pred) + ---@cast value integer|string + ---@cast capture_id integer + ---@cast key string if not metadata[capture_id] then metadata[capture_id] = {} end metadata[capture_id][key] = value else + ---@diagnostic disable-next-line:no-unknown local _, key, value = unpack(pred) + ---@cast value integer|string + ---@cast key string -- (#set! "key" "value") metadata[key] = value end @@ -393,9 +429,11 @@ local directive_handlers = { -- Shifts the range of a node. -- Example: (#offset! @_node 0 1 0 -1) ['offset!'] = function(match, _, _, pred, metadata) + ---@cast pred integer[] local capture_id = pred[2] local offset_node = match[capture_id] local range = { offset_node:range() } + ---@cast range integer[] bug in sumneko local start_row_offset = pred[3] or 0 local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 @@ -419,8 +457,9 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) --- - see |vim.treesitter.query.add_directive()| for argument meanings +---@param force boolean function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -437,12 +476,13 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) --- - match: see |treesitter-query| --- - node-level data are accessible via `match[capture_id]` --- - pattern: see |treesitter-query| --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` +---@param force boolean function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -474,6 +514,9 @@ local function is_directive(name) end ---@private +---@param match TSMatch +---@param pattern string +---@param source integer|string function Query:match_preds(match, pattern, source) local preds = self.info.patterns[pattern] @@ -482,8 +525,9 @@ function Query:match_preds(match, pattern, source) -- continue on the other case. This way unknown predicates will not be considered, -- which allows some testing and easier user extensibility (#12173). -- Also, tree-sitter strips the leading # from predicates for us. - local pred_name - local is_not + local pred_name ---@type string + + local is_not ---@type boolean -- Skip over directives... they will get processed after all the predicates. if not is_directive(pred[1]) then @@ -513,6 +557,8 @@ function Query:match_preds(match, pattern, source) end ---@private +---@param match TSMatch +---@param metadata TSMetadata function Query:apply_directives(match, pattern, source, metadata) local preds = self.info.patterns[pattern] @@ -534,6 +580,10 @@ end -- When the node's range is used, the stop is incremented by 1 -- to make the search inclusive. ---@private +---@param start integer +---@param stop integer +---@param node TSNode +---@return integer, integer local function value_or_node_range(start, stop, node) if start == nil and stop == nil then local node_start, _, node_stop, _ = node:range() @@ -565,14 +615,12 @@ end --- end --- --- ----@param node userdata |tsnode| under which the search will occur ----@param source (number|string) Source buffer or string to extract text from +---@param node TSNode under which the search will occur +---@param source (integer|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) --- ----@return number capture Matching capture id ----@return table capture_node Capture for {node} ----@return table metadata for the {capture} +---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -622,14 +670,12 @@ end --- end --- --- ----@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) +---@param node TSNode under which the search will occur +---@param source (integer|string) Source buffer or string to search +---@param start integer Starting line for the search +---@param stop integer Stopping line for the search (end-exclusive) --- ----@return number pattern id ----@return table match ----@return table metadata +---@return (fun(): integer, table, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -638,6 +684,7 @@ function Query:iter_matches(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) local raw_iter = node:_rawquery(self.query, false, start, stop) + ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() local metadata = {} -- cgit From bb8845340b1b9c2180fb19f049ff9deff5857d99 Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 21 Jul 2022 12:08:37 +0100 Subject: feat(treesitter): allow capture text to be transformed Co-authored-by: Lewis Russell --- runtime/lua/vim/treesitter/query.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 84ed2667b9..5ec8c67462 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -452,6 +452,21 @@ local directive_handlers = { metadata[capture_id].range = range end end, + + -- Transform the content of the node + -- Example: (#gsub! @_node ".*%.(.*)" "%1") + ['gsub!'] = function(match, _, bufnr, pred, metadata) + assert(#pred == 4) + + local id = pred[2] + local node = match[id] + local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' + + if not metadata[id] then + metadata[id] = {} + end + metadata[id].text = text:gsub(pred[3], pred[4]) + end, } --- Adds a new predicate to be used in queries -- cgit From e1d5ad1cb87d43c3d75619e239312d4ab2029b45 Mon Sep 17 00:00:00 2001 From: figsoda Date: Mon, 26 Dec 2022 16:10:59 -0500 Subject: feat(treesitter): add metadata option for get_node_text --- runtime/lua/vim/treesitter/query.lua | 70 +++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 28 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5ec8c67462..9136b596be 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -55,6 +55,38 @@ local function add_included_lang(base_langs, lang, ilang) return false end +---@private +---@param buf (number) +---@param range (table) +---@param concat (boolean) +---@returns (string[]|string|nil) +local function buf_range_get_text(buf, range, concat) + local lines + local start_row, start_col, end_row, end_col = unpack(range) + local eof_row = a.nvim_buf_line_count(buf) + if start_row >= eof_row then + return nil + end + + if end_col == 0 then + lines = a.nvim_buf_get_lines(buf, start_row, end_row, true) + end_col = -1 + else + lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true) + end + + if #lines > 0 then + if #lines == 1 then + lines[1] = string.sub(lines[1], start_col + 1, end_col) + else + lines[1] = string.sub(lines[1], start_col + 1) + lines[#lines] = string.sub(lines[#lines], 1, end_col) + end + end + + return concat and table.concat(lines, '\n') or lines +end + --- Gets the list of files used to make up a query --- ---@param lang string Language to get query for @@ -240,40 +272,22 @@ end ---@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) +--- - metadata (table) Metadata of a specific capture. This would be +--- set to `metadata[capture_id]` when using +--- |vim.treesitter.query.add_directive()|. ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) + local metadata = opts.metadata or {} - local start_row, start_col, start_byte = node:start() - local end_row, end_col, end_byte = node:end_() - - if type(source) == 'number' then - local eof_row = a.nvim_buf_line_count(source) - if start_row >= eof_row then - return nil - end - - local lines ---@type string[] - if end_col == 0 then - lines = a.nvim_buf_get_lines(source, start_row, end_row, true) - end_col = -1 - else - lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true) - end - - if #lines > 0 then - if #lines == 1 then - lines[1] = string.sub(lines[1], start_col + 1, end_col) - else - lines[1] = string.sub(lines[1], start_col + 1) - lines[#lines] = string.sub(lines[#lines], 1, end_col) - end - end - - return concat and table.concat(lines, '\n') or lines + if metadata.text then + return metadata.text + elseif type(source) == 'number' then + return metadata.range and buf_range_get_text(source, metadata.range, concat) + or buf_range_get_text(source, { node:range() }, concat) elseif type(source) == 'string' then - return source:sub(start_byte + 1, end_byte) + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) end end -- cgit From 4c66f5ff97a52fbc933fdbe1907c4b960d5a7403 Mon Sep 17 00:00:00 2001 From: figsoda Date: Mon, 26 Dec 2022 16:11:45 -0500 Subject: feat(treesitter): respect metadata[id].range for offset! --- runtime/lua/vim/treesitter/query.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 9136b596be..a0522d7cda 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -445,9 +445,11 @@ local directive_handlers = { ['offset!'] = function(match, _, _, pred, metadata) ---@cast pred integer[] local capture_id = pred[2] - local offset_node = match[capture_id] - local range = { offset_node:range() } - ---@cast range integer[] bug in sumneko + if not metadata[capture_id] then + metadata[capture_id] = {} + end + + local range = metadata[capture_id].range or { match[capture_id]:range() } local start_row_offset = pred[3] or 0 local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 @@ -460,9 +462,6 @@ local directive_handlers = { -- If this produces an invalid range, we just skip it. if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then - if not metadata[capture_id] then - metadata[capture_id] = {} - end metadata[capture_id].range = range end end, -- cgit From a289e82142fdc5ff657dd30198546eeb1e115fe9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 15 Feb 2023 12:26:07 +0000 Subject: fix(treesitter): make params optional --- runtime/lua/vim/treesitter/query.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index a0522d7cda..008e5a54d7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -487,7 +487,7 @@ local directive_handlers = { ---@param name string Name of the predicate, without leading # ---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param force boolean +---@param force boolean|nil function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -510,7 +510,7 @@ end --- - pattern: see |treesitter-query| --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` ----@param force boolean +---@param force boolean|nil function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) -- cgit From 8714a4009c0f0be0bb27a6b3eb486eeb3d9f3049 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 21 Feb 2023 17:09:18 +0000 Subject: feat(treesitter): add filetype -> lang API Problem: vim.treesitter does not know how to map a specific filetype to a parser. This creates problems since in a few places (including in vim.treesitter itself), the filetype is incorrectly used in place of lang. Solution: Add an API to enable this: - Add vim.treesitter.language.add() as a replacement for vim.treesitter.language.require_language(). - Optional arguments are now passed via an opts table. - Also takes a filetype (or list of filetypes) so we can keep track of what filetypes are associated with which langs. - Deprecated vim.treesitter.language.require_language(). - Add vim.treesitter.language.get_lang() which returns the associated lang for a given filetype. - Add vim.treesitter.language.register() to associate filetypes to a lang without loading the parser. --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 008e5a54d7..83910316a6 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -252,7 +252,7 @@ end) --- ---@return Query Parsed query function M.parse_query(lang, query) - language.require_language(lang) + language.add(lang) local cached = query_cache[lang][query] if cached then return cached -- cgit From 6dfbeb0d990d24657754463c6ab155c19e7f5f56 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 21 Feb 2023 17:39:29 +0100 Subject: docs: fix more treesitter parsing errors --- runtime/lua/vim/treesitter/query.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 83910316a6..58a29f2fe0 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -639,7 +639,7 @@ end --- -- typically useful info about the node: --- local type = node:type() -- type of the captured node --- local row1, col1, row2, col2 = node:range() -- range of the capture ---- ... use the info here ... +--- -- ... use the info here ... --- end --- --- @@ -693,7 +693,7 @@ end --- --- local node_data = metadata[id] -- Node level metadata --- ---- ... use the info here ... +--- -- ... use the info here ... --- end --- end --- -- cgit From 75e53341f37eeeda7d9be7f934249f7e5e4397e9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Feb 2023 15:19:52 +0000 Subject: perf(treesitter): smarter languagetree invalidation Problem: Treesitter injections are slow because all injected trees are invalidated on every change. Solution: Implement smarter invalidation to avoid reparsing injected regions. - In on_bytes, try and update self._regions as best we can. This PR just offsets any regions after the change. - Add valid flags for each region in self._regions. - Call on_bytes recursively for all children. - We still need to run the query every time for the top level tree. I don't know how to avoid this. However, if the new injection ranges don't change, then we re-use the old trees and avoid reparsing children. This should result in roughly a 2-3x reduction in tree parsing when the comment injections are enabled. --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 58a29f2fe0..13d98a0625 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -406,7 +406,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ---@field [integer] TSMetadata ---@field [string] integer|string ----@field range Range +---@field range Range4 ---@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) -- cgit From 8414cfe7f4d8888698343cb54a3f373a28b365db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 2 Mar 2023 20:46:59 +0100 Subject: docs: fix vim.treesitter tags Problem: Help tags like vim.treesitter.language.add() are confusing because `vim.treesitter.language` is (thankfully) not a user-facing module. Solution: Ignore the "fstem" when generating "treesitter" tags. --- runtime/lua/vim/treesitter/query.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 13d98a0625..4e9871b59d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -273,8 +273,7 @@ end ---@param opts (table|nil) Optional parameters. --- - concat: (boolean) Concatenate result in a string (default true) --- - metadata (table) Metadata of a specific capture. This would be ---- set to `metadata[capture_id]` when using ---- |vim.treesitter.query.add_directive()|. +--- set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|. ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} @@ -486,7 +485,7 @@ local directive_handlers = { --- ---@param name string Name of the predicate, without leading # ---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) ---- - see |vim.treesitter.query.add_directive()| for argument meanings +--- - see |vim.treesitter.add_directive()| for argument meanings ---@param force boolean|nil function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then -- cgit From 128b82103ba477482bdfaf10ec71e0986876cca1 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 4 Mar 2023 22:04:05 +0900 Subject: docs(treesitter): number → integer (#22513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- runtime/lua/vim/treesitter/query.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 4e9871b59d..22f706585e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -56,7 +56,7 @@ local function add_included_lang(base_langs, lang, ilang) end ---@private ----@param buf (number) +---@param buf (integer) ---@param range (table) ---@param concat (boolean) ---@returns (string[]|string|nil) @@ -269,7 +269,7 @@ end --- Gets the text corresponding to a given node --- ---@param node TSNode ----@param source (number|string) Buffer or string from which the {node} is extracted +---@param source (integer|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) --- - metadata (table) Metadata of a specific capture. This would be @@ -484,7 +484,7 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) +---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[]) --- - see |vim.treesitter.add_directive()| for argument meanings ---@param force boolean|nil function M.add_predicate(name, handler, force) @@ -503,7 +503,7 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) +---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[], metadata:table) --- - match: see |treesitter-query| --- - node-level data are accessible via `match[capture_id]` --- - pattern: see |treesitter-query| @@ -644,8 +644,8 @@ end --- ---@param node TSNode under which the search will occur ---@param source (integer|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) +---@param start integer Starting line for the search +---@param stop integer Stopping line for the search (end-exclusive) --- ---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) -- cgit From ddd257f75301a50c177fc24a693d39a45b47a689 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 11:03:11 +0000 Subject: feat(treesitter): use upstream format for injection queries --- runtime/lua/vim/treesitter/query.lua | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 22f706585e..59894cc7f5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -407,7 +407,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@field [string] integer|string ---@field range Range4 ----@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) +---@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata) -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) @@ -419,24 +419,17 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@type table local directive_handlers = { ['set!'] = function(_, _, _, pred, metadata) - if #pred == 4 then - -- (#set! @capture "key" "value") - ---@diagnostic disable-next-line:no-unknown - local _, capture_id, key, value = unpack(pred) - ---@cast value integer|string - ---@cast capture_id integer - ---@cast key string + if #pred >= 3 and type(pred[2]) == 'number' then + -- (#set! @capture key value) + local capture_id, key, value = pred[2], pred[3], pred[4] if not metadata[capture_id] then metadata[capture_id] = {} end metadata[capture_id][key] = value else - ---@diagnostic disable-next-line:no-unknown - local _, key, value = unpack(pred) - ---@cast value integer|string - ---@cast key string - -- (#set! "key" "value") - metadata[key] = value + -- (#set! key value) + local key, value = pred[2], pred[3] + metadata[key] = value or true end end, -- Shifts the range of a node. -- cgit From 276b647fdba07bf1762d8dd371c4b655b8a418df Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 17:22:28 +0000 Subject: refactor(treesitter): delegate region calculation to treesitter (#22553) --- runtime/lua/vim/treesitter/query.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 59894cc7f5..e7cf42283d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,6 +277,7 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} + -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From b9f19d3e286d95d9209afbc479fa2eb908067fb1 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 17:59:45 +0000 Subject: Revert "refactor(treesitter): delegate region calculation to treesitter" (#22575) Revert "refactor(treesitter): delegate region calculation to treesitter (#22553)" This reverts commit 276b647fdba07bf1762d8dd371c4b655b8a418df. --- runtime/lua/vim/treesitter/query.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e7cf42283d..59894cc7f5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,7 +277,6 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} - -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From ae263aff9547b8b513c4fedaceb4cbf93c57b866 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Mar 2023 16:09:39 +0000 Subject: refactor(treesitter): use byte ranges from treesitter (#22589) --- runtime/lua/vim/treesitter/query.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 59894cc7f5..e7cf42283d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,6 +277,7 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} + -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From 9d70fe062ca01ac0673faa6ccbb88345916aeea7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 16:10:05 +0000 Subject: feat(treesitter)!: consolidate query util functions - And address more type errors. - Removed the `concat` option from `get_node_text` since it was applied inconsistently and made typing awkward. --- runtime/lua/vim/treesitter/query.lua | 97 ++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 49 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e7cf42283d..70af4f7bce 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,6 +1,8 @@ local a = vim.api local language = require('vim.treesitter.language') +local Range = require('vim.treesitter._range') + ---@class Query ---@field captures string[] List of captures used in query ---@field info TSQueryInfo Contains used queries, predicates, directives @@ -56,35 +58,13 @@ local function add_included_lang(base_langs, lang, ilang) end ---@private ----@param buf (integer) ----@param range (table) ----@param concat (boolean) ----@returns (string[]|string|nil) -local function buf_range_get_text(buf, range, concat) - local lines - local start_row, start_col, end_row, end_col = unpack(range) - local eof_row = a.nvim_buf_line_count(buf) - if start_row >= eof_row then - return nil - end - - if end_col == 0 then - lines = a.nvim_buf_get_lines(buf, start_row, end_row, true) - end_col = -1 - else - lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true) - end - - if #lines > 0 then - if #lines == 1 then - lines[1] = string.sub(lines[1], start_col + 1, end_col) - else - lines[1] = string.sub(lines[1], start_col + 1) - lines[#lines] = string.sub(lines[#lines], 1, end_col) - end - end - - return concat and table.concat(lines, '\n') or lines +---@param buf integer +---@param range Range6 +---@returns string +local function buf_range_get_text(buf, range) + local start_row, start_col, end_row, end_col = Range.unpack4(range) + local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) + return table.concat(lines, '\n') end --- Gets the list of files used to make up a query @@ -256,14 +236,28 @@ function M.parse_query(lang, query) local cached = query_cache[lang][query] if cached then return cached - else - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) - self.info = self.query:inspect() - self.captures = self.info.captures - query_cache[lang][query] = self - return self end + + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + query_cache[lang][query] = self + return self +end + +---Get the range of a |TSNode|. Can also supply {source} and {metadata} +---to get the range with directives applied. +---@param node TSNode +---@param source integer|string|nil Buffer or string from which the {node} is extracted +---@param metadata TSMetadata|nil +---@return Range6 +function M.get_range(node, source, metadata) + if metadata and metadata.range then + assert(source) + return Range.add_bytes(source, metadata.range) + end + return { node:range(true) } end --- Gets the text corresponding to a given node @@ -271,24 +265,22 @@ end ---@param node TSNode ---@param source (integer|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) --- - metadata (table) Metadata of a specific capture. This would be --- set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|. ----@return (string[]|string|nil) +---@return string function M.get_node_text(node, source, opts) opts = opts or {} - -- TODO(lewis6991): concat only works when source is number. - local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} if metadata.text then return metadata.text elseif type(source) == 'number' then - return metadata.range and buf_range_get_text(source, metadata.range, concat) - or buf_range_get_text(source, { node:range() }, concat) - elseif type(source) == 'string' then - return source:sub(select(3, node:start()) + 1, select(3, node:end_())) + local range = M.get_range(node, source, metadata) + return buf_range_get_text(source, range) end + + ---@cast source string + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) end ---@alias TSMatch table @@ -312,7 +304,7 @@ local predicate_handlers = { str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) --[[@as string]] + str = M.get_node_text(match[predicate[3]], source) end if node_text ~= str or str == nil then @@ -328,7 +320,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source) --[[@as string]], regex) ~= nil + return string.find(M.get_node_text(node, source), regex) ~= nil end, ['match?'] = (function() @@ -366,7 +358,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) --[[@as string]] + local node_text = M.get_node_text(node, source) for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -404,9 +396,9 @@ local predicate_handlers = { predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata +---@field range Range4|Range6 ---@field [integer] TSMetadata ---@field [string] integer|string ----@field range Range4 ---@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata) @@ -465,13 +457,20 @@ local directive_handlers = { assert(#pred == 4) local id = pred[2] + assert(type(id) == 'number') + local node = match[id] local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' if not metadata[id] then metadata[id] = {} end - metadata[id].text = text:gsub(pred[3], pred[4]) + + local pattern, replacement = pred[3], pred[3] + assert(type(pattern) == 'string') + assert(type(replacement) == 'string') + + metadata[id].text = text:gsub(pattern, replacement) end, } -- cgit From 58bbc2ea0b3dfed13471e8cc0447d7598be24276 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 16:40:27 +0000 Subject: refactor(treesitter): add Range type aliase for Range4|Range6 --- runtime/lua/vim/treesitter/query.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 70af4f7bce..f4e038b2d8 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -59,10 +59,18 @@ end ---@private ---@param buf integer ----@param range Range6 +---@param range Range ---@returns string local function buf_range_get_text(buf, range) local start_row, start_col, end_row, end_col = Range.unpack4(range) + if end_col == 0 then + if start_row == end_row then + start_col = -1 + start_row = start_row - 1 + end + end_col = -1 + end_row = end_row - 1 + end local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) return table.concat(lines, '\n') end @@ -396,7 +404,7 @@ local predicate_handlers = { predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ----@field range Range4|Range6 +---@field range Range ---@field [integer] TSMetadata ---@field [string] integer|string -- cgit From cbbf8bd666c8419fdab80a0887948c8a36279c19 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Mar 2023 14:43:14 +0000 Subject: feat(treesitter)!: deprecate top level indexes to modules (#22761) The following top level Treesitter functions have been moved: - vim.treesitter.inspect_language() -> vim.treesitter.language.inspect() - vim.treesitter.get_query_files() -> vim.treesitter.query.get_files() - vim.treesitter.set_query() -> vim.treesitter.query.set() - vim.treesitter.query.set_query() -> vim.treesitter.query.set() - vim.treesitter.get_query() -> vim.treesitter.query.get() - vim.treesitter.query.get_query() -> vim.treesitter.query.get() - vim.treesitter.parse_query() -> vim.treesitter.query.parse() - vim.treesitter.query.parse_query() -> vim.treesitter.query.parse() - vim.treesitter.add_predicate() -> vim.treesitter.query.add_predicate() - vim.treesitter.add_directive() -> vim.treesitter.query.add_directive() - vim.treesitter.list_predicates() -> vim.treesitter.query.list_predicates() - vim.treesitter.list_directives() -> vim.treesitter.query.list_directives() - vim.treesitter.query.get_range() -> vim.treesitter.get_range() - vim.treesitter.query.get_node_text() -> vim.treesitter.get_node_text() --- runtime/lua/vim/treesitter/query.lua | 123 ++++++++++++++++------------------- 1 file changed, 55 insertions(+), 68 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index f4e038b2d8..8ccd6da8a7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,8 +1,6 @@ local a = vim.api local language = require('vim.treesitter.language') -local Range = require('vim.treesitter._range') - ---@class Query ---@field captures string[] List of captures used in query ---@field info TSQueryInfo Contains used queries, predicates, directives @@ -14,6 +12,7 @@ Query.__index = Query ---@field captures table ---@field patterns table +---@class TSQueryModule local M = {} ---@private @@ -57,22 +56,14 @@ local function add_included_lang(base_langs, lang, ilang) return false end ----@private ----@param buf integer ----@param range Range ----@returns string -local function buf_range_get_text(buf, range) - local start_row, start_col, end_row, end_col = Range.unpack4(range) - if end_col == 0 then - if start_row == end_row then - start_col = -1 - start_row = start_row - 1 - end - end_col = -1 - end_row = end_row - 1 - end - local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) - return table.concat(lines, '\n') +---@deprecated +function M.get_query_files(...) + vim.deprecate( + 'vim.treesitter.query.get_query_files()', + 'vim.treesitter.query.get_files()', + '0.10' + ) + return M.get_files(...) end --- Gets the list of files used to make up a query @@ -81,7 +72,7 @@ end ---@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) +function M.get_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)) @@ -153,7 +144,7 @@ function M.get_query_files(lang, query_name, is_included) local query_files = {} for _, base_lang in ipairs(base_langs) do - local base_files = M.get_query_files(base_lang, query_name, true) + local base_files = M.get_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end vim.list_extend(query_files, { base_query }) @@ -175,7 +166,7 @@ local function read_query_files(filenames) return table.concat(contents, '') end --- The explicitly set queries from |vim.treesitter.query.set_query()| +-- The explicitly set queries from |vim.treesitter.query.set()| ---@type table> local explicit_queries = setmetatable({}, { __index = function(t, k) @@ -186,6 +177,12 @@ local explicit_queries = setmetatable({}, { end, }) +---@deprecated +function M.set_query(...) + vim.deprecate('vim.treesitter.query.set_query()', 'vim.treesitter.query.set()', '0.10') + M.set(...) +end + --- Sets the runtime query named {query_name} for {lang} --- --- This allows users to override any runtime files and/or configuration @@ -194,8 +191,17 @@ local explicit_queries = setmetatable({}, { ---@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) +function M.set(lang, query_name, text) + explicit_queries[lang][query_name] = M.parse(lang, text) +end + +---@deprecated +---@param lang string Language to use for the query +---@param query_name string Name of the query (e.g. "highlights") +--- +---@return Query|nil Parsed query +function M.get_query(lang, query_name) + return M.get(lang, query_name) end --- Returns the runtime query {query_name} for {lang}. @@ -204,16 +210,16 @@ end ---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query|nil Parsed query -function M.get_query(lang, query_name) +function M.get(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] end - local query_files = M.get_query_files(lang, query_name) + local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) if #query_string > 0 then - return M.parse_query(lang, query_string) + return M.parse(lang, query_string) end end @@ -222,6 +228,12 @@ local query_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) +---@deprecated +function M.parse_query(...) + vim.deprecate('vim.treesitter.query.parse_query()', 'vim.treesitter.query.parse()', '0.10') + return M.parse(...) +end + --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). --- @@ -239,7 +251,7 @@ end) ---@param query string Query in s-expr syntax --- ---@return Query Parsed query -function M.parse_query(lang, query) +function M.parse(lang, query) language.add(lang) local cached = query_cache[lang][query] if cached then @@ -254,41 +266,16 @@ function M.parse_query(lang, query) return self end ----Get the range of a |TSNode|. Can also supply {source} and {metadata} ----to get the range with directives applied. ----@param node TSNode ----@param source integer|string|nil Buffer or string from which the {node} is extracted ----@param metadata TSMetadata|nil ----@return Range6 -function M.get_range(node, source, metadata) - if metadata and metadata.range then - assert(source) - return Range.add_bytes(source, metadata.range) - end - return { node:range(true) } +---@deprecated +function M.get_range(...) + vim.deprecate('vim.treesitter.query.get_range()', 'vim.treesitter.get_range()', '0.10') + return vim.treesitter.get_range(...) end ---- Gets the text corresponding to a given node ---- ----@param node TSNode ----@param source (integer|string) Buffer or string from which the {node} is extracted ----@param opts (table|nil) Optional parameters. ---- - metadata (table) Metadata of a specific capture. This would be ---- set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|. ----@return string -function M.get_node_text(node, source, opts) - opts = opts or {} - local metadata = opts.metadata or {} - - if metadata.text then - return metadata.text - elseif type(source) == 'number' then - local range = M.get_range(node, source, metadata) - return buf_range_get_text(source, range) - end - - ---@cast source string - return source:sub(select(3, node:start()) + 1, select(3, node:end_())) +---@deprecated +function M.get_node_text(...) + vim.deprecate('vim.treesitter.query.get_node_text()', 'vim.treesitter.get_node_text()', '0.10') + return vim.treesitter.get_node_text(...) end ---@alias TSMatch table @@ -304,7 +291,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) local str ---@type string if type(predicate[3]) == 'string' then @@ -312,7 +299,7 @@ local predicate_handlers = { str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) + str = vim.treesitter.get_node_text(match[predicate[3]], source) end if node_text ~= str or str == nil then @@ -328,7 +315,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source), regex) ~= nil + return string.find(vim.treesitter.get_node_text(node, source), regex) ~= nil end, ['match?'] = (function() @@ -357,7 +344,7 @@ local predicate_handlers = { end ---@diagnostic disable-next-line no-unknown local regex = compiled_vim_regexes[pred[3]] - return regex:match_str(M.get_node_text(node, source)) + return regex:match_str(vim.treesitter.get_node_text(node, source)) end end)(), @@ -366,7 +353,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -382,7 +369,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) -- Since 'predicate' will not be used by callers of this function, use it -- to store a string set built from the list of words to check against. @@ -468,7 +455,7 @@ local directive_handlers = { assert(type(id) == 'number') local node = match[id] - local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' + local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' if not metadata[id] then metadata[id] = {} @@ -486,7 +473,7 @@ local directive_handlers = { --- ---@param name string Name of the predicate, without leading # ---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[]) ---- - see |vim.treesitter.add_directive()| for argument meanings +--- - see |vim.treesitter.query.add_directive()| for argument meanings ---@param force boolean|nil function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then -- cgit From ac7397f4a06e451fedde86fb4eba0038d0d75e68 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Mar 2023 16:31:30 +0000 Subject: fix(treesitter): add missing deprecate --- runtime/lua/vim/treesitter/query.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8ccd6da8a7..25623c1498 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -196,12 +196,9 @@ function M.set(lang, query_name, text) end ---@deprecated ----@param lang string Language to use for the query ----@param query_name string Name of the query (e.g. "highlights") ---- ----@return Query|nil Parsed query -function M.get_query(lang, query_name) - return M.get(lang, query_name) +function M.get_query(...) + vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') + return M.get(...) end --- Returns the runtime query {query_name} for {lang}. -- cgit From 34ac75b32927328a0c691c5bda987c0fdb5ce9eb Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 5 Apr 2023 17:19:53 +0100 Subject: refactor: rename local API alias from a to api Problem: Codebase inconsistently binds vim.api onto a or api. Solution: Use api everywhere. a as an identifier is too short to have at the module level. --- runtime/lua/vim/treesitter/query.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 25623c1498..5b87e6ac31 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api local language = require('vim.treesitter.language') ---@class Query @@ -74,7 +74,7 @@ end ---@return string[] query_files List of files to load for given query and language function M.get_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)) + local lang_files = dedupe_files(api.nvim_get_runtime_file(query_path, true)) if #lang_files == 0 then return {} @@ -635,7 +635,7 @@ end ---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then - source = vim.api.nvim_get_current_buf() + source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) @@ -690,7 +690,7 @@ end ---@return (fun(): integer, table, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then - source = vim.api.nvim_get_current_buf() + source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) -- cgit From ccc0980f86c6ef9a86b0e5a3a691f37cea8eb776 Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Tue, 11 Apr 2023 16:26:03 +0800 Subject: fix(treesitter): Use the correct replacement args for #gsub! directive (#23015) fix(treesitter): use the correct replacement args for #gsub! directive --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5b87e6ac31..8a747ba14c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -458,7 +458,7 @@ local directive_handlers = { metadata[id] = {} end - local pattern, replacement = pred[3], pred[3] + local pattern, replacement = pred[3], pred[4] assert(type(pattern) == 'string') assert(type(replacement) == 'string') -- cgit From c194acbfc479d8e5839fa629363f93f6550d035c Mon Sep 17 00:00:00 2001 From: Stephan Seitz Date: Sat, 29 Apr 2023 18:22:26 +0200 Subject: feat(treesitter): add query_linter from nvim-treesitter/playground (#22784) Co-authored-by: clason Co-authored-by: lewis6991 --- runtime/lua/vim/treesitter/query.lua | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8a747ba14c..492bfd1ffb 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -714,4 +714,33 @@ function Query:iter_matches(node, source, start, stop) return iter end +---@class QueryLinterOpts +---@field langs (string|string[]|nil) +---@field clear (boolean) + +--- Lint treesitter queries using installed parser, or clear lint errors. +--- +--- Use |treesitter-parsers| in runtimepath to check the query file in {buf} for errors: +--- +--- - verify that used nodes are valid identifiers in the grammar. +--- - verify that predicates and directives are valid. +--- - verify that top-level s-expressions are valid. +--- +--- The found diagnostics are reported using |diagnostic-api|. +--- By default, the parser used for verification is determined by the containing folder +--- of the query file, e.g., if the path is `**/lua/highlights.scm`, the parser for the +--- `lua` language will be used. +---@param buf (integer) Buffer handle +---@param opts (QueryLinterOpts|nil) Optional keyword arguments: +--- - langs (string|string[]|nil) Language(s) to use for checking the query. +--- If multiple languages are specified, queries are validated for all of them +--- - clear (boolean) if `true`, just clear current lint errors +function M.lint(buf, opts) + if opts and opts.clear then + require('vim.treesitter._query_linter').clear(buf) + else + require('vim.treesitter._query_linter').lint(buf, opts) + end +end + return M -- cgit From 668f16bac779ac52d7bd9452e6001a7a6d1e9965 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 30 Apr 2023 11:01:54 +0200 Subject: feat(treesitter): upstream query omnifunc from playground (#23394) and set by default in `ftplugin/query.lua` --- runtime/lua/vim/treesitter/query.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 492bfd1ffb..93841bb31e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -728,7 +728,7 @@ end --- --- The found diagnostics are reported using |diagnostic-api|. --- By default, the parser used for verification is determined by the containing folder ---- of the query file, e.g., if the path is `**/lua/highlights.scm`, the parser for the +--- of the query file, e.g., if the path ends in `/lua/highlights.scm`, the parser for the --- `lua` language will be used. ---@param buf (integer) Buffer handle ---@param opts (QueryLinterOpts|nil) Optional keyword arguments: @@ -743,4 +743,14 @@ function M.lint(buf, opts) end end +--- Omnifunc for completing node names and predicates in treesitter queries. +--- +--- Use via +---
lua
+---   vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+--- 
+function M.omnifunc(findstart, base) + return require('vim.treesitter._query_linter').omnifunc(findstart, base) +end + return M -- cgit From af040c3a079f6e25db0ad6b908aa1327f67deb82 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 11 May 2023 11:13:32 +0100 Subject: feat(treesitter): add support for setting query depths --- runtime/lua/vim/treesitter/query.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 93841bb31e..e6a117557a 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -686,16 +686,20 @@ end ---@param source (integer|string) Source buffer or string to search ---@param start integer Starting line for the search ---@param stop integer Stopping line for the search (end-exclusive) +---@param opts table|nil Options: +--- - 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. +--- Requires treesitter >= 0.20.9. --- ---@return (fun(): integer, table, table): pattern id, match, metadata -function Query:iter_matches(node, source, start, stop) +function Query:iter_matches(node, source, start, stop, opts) if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) - local raw_iter = node:_rawquery(self.query, false, start, stop) + local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() -- cgit From 08991b078267e5de0a19a136d00d4f71ad651a32 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sat, 13 May 2023 21:33:22 +0200 Subject: docs: small fixes Co-authored-by: Christian Clason Co-authored-by: Gregory Anders Co-authored-by: HiPhish Co-authored-by: Julio B Co-authored-by: T727 <74924917+T-727@users.noreply.github.com> Co-authored-by: camoz Co-authored-by: champignoom <66909116+champignoom@users.noreply.github.com> --- runtime/lua/vim/treesitter/query.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e6a117557a..75e5bf8870 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -609,10 +609,10 @@ end --- --- {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 +--- text of the buffer (if relevant). {start} and {stop} 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. +--- viewport). When omitted, the {start} and {stop} row values are used from the given node. --- --- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. -- cgit From 9ff59517cbf309d31f979a49b7dc82b237ecfcc4 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 May 2023 12:56:21 +0200 Subject: fix(treesitter): update c queries --- runtime/lua/vim/treesitter/query.lua | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 75e5bf8870..73b561c777 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -382,6 +382,39 @@ local predicate_handlers = { return string_set[node_text] end, + + ['has-ancestor?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + local ancestor_types = {} + for _, type in ipairs({ unpack(predicate, 3) }) do + ancestor_types[type] = true + end + + node = node:parent() + while node do + if ancestor_types[node:type()] then + return true + end + node = node:parent() + end + return false + end, + + ['has-parent?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then + return true + end + return false + end, } -- As we provide lua-match? also expose vim-match? -- cgit From 11844dde81c41bded54f2383b57f8eef406f2736 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 1 Jul 2023 11:08:06 +0200 Subject: feat(treesitter): bundle markdown parser and queries (#22481) * bundle split Markdown parser from https://github.com/MDeiml/tree-sitter-markdown * add queries from https://github.com/nvim-treesitter/nvim-treesitter/tree/main * upstream `#trim!` and `#inject-language!` directives Co-authored-by: dundargoc --- runtime/lua/vim/treesitter/query.lua | 60 +++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 73b561c777..7f90fa10e8 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -475,7 +475,6 @@ local directive_handlers = { metadata[capture_id].range = range end end, - -- Transform the content of the node -- Example: (#gsub! @_node ".*%.(.*)" "%1") ['gsub!'] = function(match, _, bufnr, pred, metadata) @@ -497,6 +496,65 @@ local directive_handlers = { metadata[id].text = text:gsub(pattern, replacement) end, + -- Trim blank lines from end of the node + -- Example: (#trim! @fold) + -- TODO(clason): generalize to arbitrary whitespace removal + ['trim!'] = function(match, _, bufnr, pred, metadata) + local node = match[pred[2]] + if not node then + return + end + + local start_row, start_col, end_row, end_col = node:range() + + -- Don't trim if region ends in middle of a line + if end_col ~= 0 then + return + end + + while true do + -- As we only care when end_col == 0, always inspect one line above end_row. + local end_line = vim.api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] + + if end_line ~= '' then + break + end + + end_row = end_row - 1 + end + + -- If this produces an invalid range, we just skip it. + if start_row < end_row or (start_row == end_row and start_col <= end_col) then + metadata.range = { start_row, start_col, end_row, end_col } + end + end, + -- Set injection language from node text, interpreted first as language and then as filetype + -- Example: (#inject-language! @_lang) + ['inject-language!'] = function(match, _, bufnr, pred, metadata) + local id = pred[2] + local node = match[id] + if not node then + return + end + + -- TODO(clason): replace by refactored `ts.has_parser` API + local has_parser = function(lang) + return vim._ts_has_language(lang) + or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 + end + + local alias = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) + if not alias then + return + elseif has_parser(alias) then + metadata['injection.language'] = alias + else + local lang = vim.treesitter.language.get_lang(alias) + if lang and has_parser(lang) then + metadata['injection.language'] = lang + end + end + end, } --- Adds a new predicate to be used in queries -- cgit From 4fd852b8cb88ed035203d3f9ae2e6a8258244974 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Mon, 3 Jul 2023 00:44:21 +0900 Subject: perf(treesitter): cache fold query (#24222) perf(treesitter): cache vim.treesitter.query.get Problem: vim.treesitter.query.get searches and reads query files every time it's called, if user hasn't overridden the query. So this can incur slowdown when called frequently. This can happen when using treesitter foldexpr. For example, when using `:h :range!` in markdown file to format fenced codeblock, on_changedtree in _fold.lua is triggered many times despite that the tree doesn't have syntactic changes (might be a bug in LanguageTree). (Incidentally, the resulting fold is incorrect due to a bug in `:h range!`.) on_changedtree calls vim.treesitter.query.get for each tree changes. In addition, it may request folds queries for injected languages without fold queries, such as markdown_inline. Solution: * Cache the result of vim.treesitter.query.get. * If query file was not found, fail quickly at later calls. --- runtime/lua/vim/treesitter/query.lua | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7f90fa10e8..7610ef7b7f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -195,6 +195,12 @@ function M.set(lang, query_name, text) explicit_queries[lang][query_name] = M.parse(lang, text) end +--- `false` if query files didn't exist or were empty +---@type table> +local query_get_cache = vim.defaulttable(function() + return setmetatable({}, { __mode = 'v' }) +end) + ---@deprecated function M.get_query(...) vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') @@ -212,16 +218,28 @@ function M.get(lang, query_name) return explicit_queries[lang][query_name] end + local cached = query_get_cache[lang][query_name] + if cached then + return cached + elseif cached == false then + return nil + end + local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) - if #query_string > 0 then - return M.parse(lang, query_string) + if #query_string == 0 then + query_get_cache[lang][query_name] = false + return nil end + + local query = M.parse(lang, query_string) + query_get_cache[lang][query_name] = query + return query end ----@type {[string]: {[string]: Query}} -local query_cache = vim.defaulttable(function() +---@type table> +local query_parse_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) @@ -250,7 +268,7 @@ end ---@return Query Parsed query function M.parse(lang, query) language.add(lang) - local cached = query_cache[lang][query] + local cached = query_parse_cache[lang][query] if cached then return cached end @@ -259,7 +277,7 @@ function M.parse(lang, query) self.query = vim._ts_parse_query(lang, query) self.info = self.query:inspect() self.captures = self.info.captures - query_cache[lang][query] = self + query_parse_cache[lang][query] = self return self end -- cgit From be74807eef13ff8c90d55cf8b22b01d6d33b1641 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 18 Jul 2023 15:42:30 +0100 Subject: docs(lua): more improvements (#24387) * docs(lua): teach lua2dox how to table * docs(lua): teach gen_vimdoc.py about local functions No more need to mark local functions with @private * docs(lua): mention @nodoc and @meta in dev-lua-doc * fixup! Co-authored-by: Justin M. Keyes --------- Co-authored-by: Justin M. Keyes --- runtime/lua/vim/treesitter/query.lua | 9 --------- 1 file changed, 9 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7610ef7b7f..08186468a5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -15,7 +15,6 @@ Query.__index = Query ---@class TSQueryModule local M = {} ----@private ---@param files string[] ---@return string[] local function dedupe_files(files) @@ -33,7 +32,6 @@ local function dedupe_files(files) return result end ----@private local function safe_read(filename, read_quantifier) local file, err = io.open(filename, 'r') if not file then @@ -44,7 +42,6 @@ local function safe_read(filename, read_quantifier) return content end ----@private --- Adds {ilang} to {base_langs}, only if {ilang} is different than {lang} --- ---@return boolean true If lang == ilang @@ -153,7 +150,6 @@ function M.get_files(lang, query_name, is_included) return query_files end ----@private ---@param filenames string[] ---@return string local function read_query_files(filenames) @@ -335,7 +331,6 @@ local predicate_handlers = { ['match?'] = (function() local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true } - ---@private local function check_magic(str) if string.len(str) < 2 or magic_prefixes[string.sub(str, 1, 2)] then return str @@ -624,12 +619,10 @@ function M.list_predicates() return vim.tbl_keys(predicate_handlers) end ----@private local function xor(x, y) return (x or y) and not (x and y) end ----@private local function is_directive(name) return string.sub(name, -1) == '!' end @@ -700,7 +693,6 @@ end --- Returns the start and stop value if set else the node's range. -- When the node's range is used, the stop is incremented by 1 -- to make the search inclusive. ----@private ---@param start integer ---@param stop integer ---@param node TSNode @@ -750,7 +742,6 @@ function Query:iter_captures(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) local raw_iter = node:_rawquery(self.query, true, start, stop) - ---@private local function iter() local capture, captured_node, match = raw_iter() local metadata = {} -- cgit From c43c745a14dced87a23227d7be4f1c33d4455193 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 9 Aug 2023 11:06:13 +0200 Subject: fix(lua): improve annotations for stricter luals diagnostics (#24609) Problem: luals returns stricter diagnostics with bundled luarc.json Solution: Improve some function and type annotations: * use recognized uv.* types * disable diagnostic for global `vim` in shared.lua * docs: don't start comment lines with taglink (otherwise LuaLS will interpret it as a type) * add type alias for lpeg pattern * fix return annotation for `vim.secure.trust` * rename local Range object in vim.version (shadows `Range` in vim.treesitter) * fix some "missing fields" warnings * add missing required fields for test functions in eval.lua * rename lsp meta files for consistency --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 08186468a5..3b7e74c0cf 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -434,7 +434,7 @@ local predicate_handlers = { predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ----@field range Range +---@field range? Range ---@field [integer] TSMetadata ---@field [string] integer|string -- cgit From 31c4ed26bc278282898123ad21bb6fead401fd6f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 11 Aug 2023 17:05:17 +0200 Subject: feat(treesitter): add injection language fallback (#24659) * feat(treesitter): add injection language fallback Problem: injection languages are often specified via aliases (e.g., filetype or in upper case), requiring custom directives. Solution: include lookup logic (try as parser name, then filetype, then lowercase) in LanguageTree itself and remove `#inject-language` directive. Co-authored-by: Lewis Russell --- runtime/lua/vim/treesitter/query.lua | 27 --------------------------- 1 file changed, 27 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 3b7e74c0cf..c3213e0192 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -541,33 +541,6 @@ local directive_handlers = { metadata.range = { start_row, start_col, end_row, end_col } end end, - -- Set injection language from node text, interpreted first as language and then as filetype - -- Example: (#inject-language! @_lang) - ['inject-language!'] = function(match, _, bufnr, pred, metadata) - local id = pred[2] - local node = match[id] - if not node then - return - end - - -- TODO(clason): replace by refactored `ts.has_parser` API - local has_parser = function(lang) - return vim._ts_has_language(lang) - or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 - end - - local alias = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) - if not alias then - return - elseif has_parser(alias) then - metadata['injection.language'] = alias - else - local lang = vim.treesitter.language.get_lang(alias) - if lang and has_parser(lang) then - metadata['injection.language'] = lang - end - end - end, } --- Adds a new predicate to be used in queries -- cgit From 2ca076e45fb3f1c08f6a1a374834df0701b8d778 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 10 Aug 2023 14:21:56 +0100 Subject: feat(treesitter)!: incremental injection parsing Problem: Treesitter highlighting is slow for large files with lots of injections. Solution: Only parse injections we are going to render during a redraw cycle. --- - `LanguageTree:parse()` will no longer parse injections by default and now requires an explicit range argument to be passed. - `TSHighlighter` now parses injections incrementally during on_win callbacks for the line range being rendered. - Plugins which require certain injections to be parsed must run `parser:parse({ start_row, end_row })` before using the tree. --- runtime/lua/vim/treesitter/query.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index c3213e0192..3093657313 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -435,6 +435,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ---@field range? Range +---@field conceal? string ---@field [integer] TSMetadata ---@field [string] integer|string -- cgit From 2e92065686f62851318150a315591c30b8306a4b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:23:01 -0500 Subject: docs: replace
 with ``` (#25136)

---
 runtime/lua/vim/treesitter/query.lua | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

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

diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 3093657313..350ccba7e4 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -692,7 +692,8 @@ 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
+---
+--- ```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:
@@ -700,7 +701,7 @@ end
 ---   local row1, col1, row2, col2 = node:range() -- range of the capture
 ---   -- ... use the info here ...
 --- end
---- 
+--- ``` --- ---@param node TSNode under which the search will occur ---@param source (integer|string) Source buffer or string to extract text from @@ -743,7 +744,8 @@ 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
+---
+--- ```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]
@@ -754,7 +756,7 @@ end
 ---     -- ... use the info here ...
 ---   end
 --- end
---- 
+--- ``` --- ---@param node TSNode under which the search will occur ---@param source (integer|string) Source buffer or string to search @@ -824,9 +826,11 @@ end --- Omnifunc for completing node names and predicates in treesitter queries. --- --- Use via ----
lua
----   vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
---- 
+--- +--- ```lua +--- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc' +--- ``` +--- function M.omnifunc(findstart, base) return require('vim.treesitter._query_linter').omnifunc(findstart, base) end -- cgit From 28233bcb49067aaa70fa6e5fec14e2cc4bcaa315 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 15 Sep 2023 03:10:55 -0700 Subject: refactor(treesitter): rename "preview" => "edit" #25161 "Edit" more closely describes the generic application than "Preview", though the buffer contents don't (yet) map to an actual file on disk. https://github.com/neovim/neovim/pull/24703#discussion_r1321719133 --- runtime/lua/vim/treesitter/query.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 350ccba7e4..d7973cc48f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -835,4 +835,13 @@ function M.omnifunc(findstart, base) return require('vim.treesitter._query_linter').omnifunc(findstart, base) end +--- Open a window for live editing of a treesitter query. +--- +--- Can also be shown with `:EditQuery`. *:EditQuery* +--- +--- Note that the editor opens a scratch buffer, and so queries aren't persisted on disk. +function M.edit() + require('vim.treesitter.dev').edit_query() +end + return M -- cgit From 07080f67fe7e526576d5d50777fb122a99b3e183 Mon Sep 17 00:00:00 2001 From: L Lllvvuu Date: Sat, 16 Sep 2023 02:48:49 -0700 Subject: perf(treesitter): do not scan past given line for predicate match Problem --- If a highlighter query returns a significant number of predicate non-matches, the highlighter will scan well past the end of the window. Solution --- In the iterator returned from `iter_captures`, accept an optional parameter `end_line`. If no parameter provided, the behavior is unchanged, hence this is a non-invasive tweak. Fixes: #25113 nvim-treesitter/nvim-treesitter#5057 --- runtime/lua/vim/treesitter/query.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index d7973cc48f..6d9b214d4a 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -708,7 +708,8 @@ end ---@param start integer Starting line for the search ---@param stop integer Stopping line for the search (end-exclusive) --- ----@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata +---@return (fun(end_line: integer|nil): integer, TSNode, TSMetadata): +--- capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() @@ -717,7 +718,7 @@ function Query:iter_captures(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) local raw_iter = node:_rawquery(self.query, true, start, stop) - local function iter() + local function iter(end_line) local capture, captured_node, match = raw_iter() local metadata = {} @@ -725,7 +726,10 @@ function Query:iter_captures(node, source, start, stop) local active = self:match_preds(match, match.pattern, source) match.active = active if not active then - return iter() -- tail call: try next match + if end_line and captured_node:range() > end_line then + return nil, captured_node, nil + end + return iter(end_line) -- tail call: try next match end self:apply_directives(match, match.pattern, source, metadata) -- cgit From f40a109716d7f748dd9e9f70b57e4d0bb285518b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Sep 2023 10:39:34 +0100 Subject: fix(treesitter): fix trim predicate --- runtime/lua/vim/treesitter/query.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 6d9b214d4a..e83ad00eeb 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -514,7 +514,10 @@ local directive_handlers = { -- Example: (#trim! @fold) -- TODO(clason): generalize to arbitrary whitespace removal ['trim!'] = function(match, _, bufnr, pred, metadata) - local node = match[pred[2]] + local capture_id = pred[2] + assert(type(capture_id) == 'number') + + local node = match[capture_id] if not node then return end @@ -526,9 +529,9 @@ local directive_handlers = { return end - while true do + while end_row >= start_row do -- As we only care when end_col == 0, always inspect one line above end_row. - local end_line = vim.api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] + local end_line = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] if end_line ~= '' then break @@ -539,7 +542,8 @@ local directive_handlers = { -- If this produces an invalid range, we just skip it. if start_row < end_row or (start_row == end_row and start_col <= end_col) then - metadata.range = { start_row, start_col, end_row, end_col } + metadata[capture_id] = metadata[capture_id] or {} + metadata[capture_id].range = { start_row, start_col, end_row, end_col } end end, } -- cgit From 28f54a78782318cb9c356a372b9e52a3a6b1f8dd Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 16 Sep 2023 10:05:59 -0700 Subject: feat(treesitter): add lang parameter to the query editor (#25181) --- runtime/lua/vim/treesitter/query.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e83ad00eeb..44ed37d64e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -848,8 +848,10 @@ end --- Can also be shown with `:EditQuery`. *:EditQuery* --- --- Note that the editor opens a scratch buffer, and so queries aren't persisted on disk. -function M.edit() - require('vim.treesitter.dev').edit_query() +--- +--- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype. +function M.edit(lang) + require('vim.treesitter.dev').edit_query(lang) end return M -- cgit From 1b55f51d0d8468ca357514a868ac8e188b0c8722 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 20 Sep 2023 04:15:23 -0700 Subject: docs: misc #24561 fix #24699 fix #25253 --- runtime/lua/vim/treesitter/query.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 44ed37d64e..313d837d5c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -843,11 +843,13 @@ function M.omnifunc(findstart, base) return require('vim.treesitter._query_linter').omnifunc(findstart, base) end ---- Open a window for live editing of a treesitter query. +--- Opens a live editor to query the buffer you started from. --- ---- Can also be shown with `:EditQuery`. *:EditQuery* +--- Can also be shown with *:EditQuery*. --- ---- Note that the editor opens a scratch buffer, and so queries aren't persisted on disk. +--- If you move the cursor to a capture name ("@foo"), text matching the capture is highlighted in +--- the source buffer. The query editor is a scratch buffer, use `:write` to save it. You can find +--- example queries at `$VIMRUNTIME/queries/`. --- --- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype. function M.edit(lang) -- cgit From 877d04d0fb83b5fc602dbab22b58f26a793ec236 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 16 Sep 2023 23:10:30 +0100 Subject: feat(lua): add vim.func._memoize Memoizes a function, using a custom function to hash the arguments. Private for now until: - There are other places in the codebase that could benefit from this (e.g. LSP), but might require other changes to accommodate. - Invalidation of the cache needs to be controllable. Using weak tables is an acceptable invalidation policy, but it shouldn't be the only one. - I don't think the story around `hash_fn` is completely thought out. We may be able to have a good default hash_fn by hashing each argument, so basically a better 'concat'. --- runtime/lua/vim/treesitter/query.lua | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) (limited to 'runtime/lua/vim/treesitter/query.lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 313d837d5c..8cbbffcd60 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -191,12 +191,6 @@ function M.set(lang, query_name, text) explicit_queries[lang][query_name] = M.parse(lang, text) end ---- `false` if query files didn't exist or were empty ----@type table> -local query_get_cache = vim.defaulttable(function() - return setmetatable({}, { __mode = 'v' }) -end) - ---@deprecated function M.get_query(...) vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') @@ -209,34 +203,19 @@ end ---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query|nil Parsed query -function M.get(lang, query_name) +M.get = vim.func._memoize('concat-2', function(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] end - local cached = query_get_cache[lang][query_name] - if cached then - return cached - elseif cached == false then - return nil - end - local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) if #query_string == 0 then - query_get_cache[lang][query_name] = false return nil end - local query = M.parse(lang, query_string) - query_get_cache[lang][query_name] = query - return query -end - ----@type table> -local query_parse_cache = vim.defaulttable(function() - return setmetatable({}, { __mode = 'v' }) + return M.parse(lang, query_string) end) ---@deprecated @@ -262,20 +241,15 @@ end ---@param query string Query in s-expr syntax --- ---@return Query Parsed query -function M.parse(lang, query) +M.parse = vim.func._memoize('concat-2', function(lang, query) language.add(lang) - local cached = query_parse_cache[lang][query] - if cached then - return cached - end local self = setmetatable({}, Query) self.query = vim._ts_parse_query(lang, query) self.info = self.query:inspect() self.captures = self.info.captures - query_parse_cache[lang][query] = self return self -end +end) ---@deprecated function M.get_range(...) -- cgit