From 8cbb1f20e557461c8417583a7f69d53aaaef920b Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Tue, 4 Jun 2024 09:06:02 -0400 Subject: refactor(lua): use tuple syntax everywhere #29111 --- runtime/lua/vim/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index db544c1ab1..e36aacfd94 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -335,7 +335,7 @@ end --- --- 0-indexed (row, col) tuple. Defaults to cursor position in the --- current window. Required if {bufnr} is not the current buffer ---- @field pos { [1]: integer, [2]: integer }? +--- @field pos [integer, integer]? --- --- Parser language. (default: from buffer filetype) --- @field lang string? -- cgit From 1af55bfcf21b9bc7594b9c5ee0c2f60cbb887654 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 28 Jul 2024 13:30:33 -0700 Subject: feat(treesitter): allow get_node to return anonymous nodes Adds a new field `include_anonymous` to the `get_node` options to allow anonymous nodes to be returned. --- runtime/lua/vim/treesitter.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index e36aacfd94..4629203138 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -342,6 +342,9 @@ end --- --- Ignore injected languages (default true) --- @field ignore_injections boolean? +--- +--- Include anonymous nodes (default false) +--- @field include_anonymous boolean? --- Returns the smallest named node at the given position --- @@ -388,6 +391,9 @@ function M.get_node(opts) return end + if opts.include_anonymous then + return root_lang_tree:node_for_range(ts_range, opts) + end return root_lang_tree:named_node_for_range(ts_range, opts) end -- cgit From b9b408a56c7e607972beaa7214719ff1494e384c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 13 Sep 2024 05:09:11 -0700 Subject: feat(treesitter): start moving get_parser to return nil #30313 **Problem:** `vim.treesitter.get_parser` will throw an error if no parser can be found. - This means the caller is responsible for wrapping it in a `pcall`, which is easy to forget - It also makes it slightly harder to potentially memoize `get_parser` in the future - It's a bit unintuitive since many other `get_*` style functions conventionally return `nil` if no object is found (e.g. `get_node`, `get_lang`, `query.get`, etc.) **Solution:** Return `nil` if no parser can be found or created - This requires a function signature change, and some new assertions in places where the parser will always (or should always) be found. - This commit starts by making this change internally, since it is breaking. Eventually it will be rolled out to the public API. --- runtime/lua/vim/treesitter.lua | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4629203138..809ea59b94 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -74,14 +74,14 @@ end --- Returns the parser for a specific buffer and attaches it to the buffer --- ---- If needed, this will create the parser. +--- If needed, this will create the parser. If no parser can be found or created, returns `nil`. --- ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ---@param lang (string|nil) Language of this parser (default: from buffer filetype) ---@param opts (table|nil) Options to pass to the created language tree --- ----@return vim.treesitter.LanguageTree object to use for parsing -function M.get_parser(bufnr, lang, opts) +---@return vim.treesitter.LanguageTree? object to use for parsing, or `nil` if not found +function M._get_parser(bufnr, lang, opts) opts = opts or {} if bufnr == nil or bufnr == 0 then @@ -94,18 +94,14 @@ function M.get_parser(bufnr, lang, opts) if not valid_lang(lang) then if not parsers[bufnr] then - error( - string.format( - 'There is no parser available for buffer %d and one could not be' - .. ' created because lang could not be determined. Either pass lang' - .. ' or set the buffer filetype', - bufnr - ) - ) + return nil end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then - assert(lang, 'lang should be valid') - parsers[bufnr] = M._create_parser(bufnr, lang, opts) + local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts) + if not parser then + return nil + end + parsers[bufnr] = parser end parsers[bufnr]:register_cbs(opts.buf_attach_cbs) @@ -113,6 +109,29 @@ function M.get_parser(bufnr, lang, opts) return parsers[bufnr] end +--- Returns the parser for a specific buffer and attaches it to the buffer +--- +--- If needed, this will create the parser. +--- +---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) +---@param lang (string|nil) Language of this parser (default: from buffer filetype) +---@param opts (table|nil) Options to pass to the created language tree +--- +---@return vim.treesitter.LanguageTree object to use for parsing +function M.get_parser(bufnr, lang, opts) + -- TODO(ribru17): Remove _get_parser and move that logic back here once the breaking function + -- signature change is acceptable. + local parser = M._get_parser(bufnr, lang, opts) + if not parser then + vim.notify_once( + 'WARNING: vim.treesitter.get_parser will return nil instead of raising an error in Neovim 0.12', + vim.log.levels.WARN + ) + error('Parser not found.') + end + return parser +end + --- Returns a string parser --- ---@param str string Text to parse @@ -386,7 +405,7 @@ function M.get_node(opts) local ts_range = { row, col, row, col } - local root_lang_tree = M.get_parser(bufnr, opts.lang) + local root_lang_tree = M._get_parser(bufnr, opts.lang) if not root_lang_tree then return end @@ -419,7 +438,11 @@ end ---@param lang (string|nil) Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = bufnr or api.nvim_get_current_buf() - local parser = M.get_parser(bufnr, lang) + local parser = M._get_parser(bufnr, lang) + if not parser then + vim.notify('No parser for the given buffer.', vim.log.levels.WARN) + return + end M.highlighter.new(parser) end -- cgit From 64847fbdc908bf0a301b8f1e1814ff71bd425bae Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Wed, 25 Sep 2024 11:33:14 -0700 Subject: perf(treesitter): use `child_containing_descendant()` in `is_ancestor()` **Problem:** `is_ancestor()` uses a slow, bottom-up parent lookup which has performance pitfalls detailed in #28512. **Solution:** Take `is_ancestor()` from $O(n^2)$ to $O(n)$ by incorporating the use of the `child_containing_descendant()` function --- runtime/lua/vim/treesitter.lua | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 809ea59b94..baf47482a8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -159,16 +159,8 @@ function M.is_ancestor(dest, source) return false end - local current = source ---@type TSNode? - while current ~= nil do - if current == dest then - return true - end - - current = current:parent() - end - - return false + -- child_containing_descendant returns nil if dest is a direct parent + return source:parent() == dest or dest:child_containing_descendant(source) ~= nil end --- Returns the node's range or an unpacked range table -- cgit From 0f067cd34d09b38f9aaf2e1732d825e89b573077 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 14 Sep 2024 12:57:33 -0700 Subject: fix(treesitter): suppress get_parser warnings via opts.error --- runtime/lua/vim/treesitter.lua | 57 +++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index baf47482a8..8d5cd2eeec 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -74,15 +74,21 @@ end --- Returns the parser for a specific buffer and attaches it to the buffer --- ---- If needed, this will create the parser. If no parser can be found or created, returns `nil`. +--- If needed, this will create the parser. +--- +--- If no parser can be created, an error is thrown. Set `opts.error = false` to suppress this and +--- return nil (and an error message) instead. WARNING: This behavior will become default in Nvim +--- 0.12 and the option will be removed. --- ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ---@param lang (string|nil) Language of this parser (default: from buffer filetype) ---@param opts (table|nil) Options to pass to the created language tree --- ----@return vim.treesitter.LanguageTree? object to use for parsing, or `nil` if not found -function M._get_parser(bufnr, lang, opts) +---@return vim.treesitter.LanguageTree? object to use for parsing +---@return string? error message, if applicable +function M.get_parser(bufnr, lang, opts) opts = opts or {} + local should_error = opts.error == nil or opts.error if bufnr == nil or bufnr == 0 then bufnr = api.nvim_get_current_buf() @@ -94,12 +100,22 @@ function M._get_parser(bufnr, lang, opts) if not valid_lang(lang) then if not parsers[bufnr] then - return nil + local err_msg = + string.format('Parser not found for buffer %s: language could not be determined', bufnr) + if should_error then + error(err_msg) + end + return nil, err_msg end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts) if not parser then - return nil + local err_msg = + string.format('Parser could not be created for buffer %s and language "%s"', bufnr, lang) + if should_error then + error(err_msg) + end + return nil, err_msg end parsers[bufnr] = parser end @@ -109,29 +125,6 @@ function M._get_parser(bufnr, lang, opts) return parsers[bufnr] end ---- Returns the parser for a specific buffer and attaches it to the buffer ---- ---- If needed, this will create the parser. ---- ----@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ----@param lang (string|nil) Language of this parser (default: from buffer filetype) ----@param opts (table|nil) Options to pass to the created language tree ---- ----@return vim.treesitter.LanguageTree object to use for parsing -function M.get_parser(bufnr, lang, opts) - -- TODO(ribru17): Remove _get_parser and move that logic back here once the breaking function - -- signature change is acceptable. - local parser = M._get_parser(bufnr, lang, opts) - if not parser then - vim.notify_once( - 'WARNING: vim.treesitter.get_parser will return nil instead of raising an error in Neovim 0.12', - vim.log.levels.WARN - ) - error('Parser not found.') - end - return parser -end - --- Returns a string parser --- ---@param str string Text to parse @@ -397,7 +390,7 @@ function M.get_node(opts) local ts_range = { row, col, row, col } - local root_lang_tree = M._get_parser(bufnr, opts.lang) + local root_lang_tree = M.get_parser(bufnr, opts.lang, { error = false }) if not root_lang_tree then return end @@ -430,11 +423,7 @@ end ---@param lang (string|nil) Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = bufnr or api.nvim_get_current_buf() - local parser = M._get_parser(bufnr, lang) - if not parser then - vim.notify('No parser for the given buffer.', vim.log.levels.WARN) - return - end + local parser = assert(M.get_parser(bufnr, lang, { error = false })) M.highlighter.new(parser) end -- cgit From 2c937d723dd6f64705bf4d901254683ba32b2846 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:54:12 +0200 Subject: docs: misc (#30177) Co-authored-by: Christian Clason Co-authored-by: Riley Bruins Co-authored-by: zeertzjq --- runtime/lua/vim/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 8d5cd2eeec..c21e0aff07 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -261,7 +261,7 @@ end ---@param row integer Position row ---@param col integer Position column --- ----@return {capture: string, lang: string, metadata: table}[] +---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[] function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then bufnr = api.nvim_get_current_buf() -- cgit From 041d98fe8d892a81ed659c32be5360d4f80e7d18 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 14 Sep 2024 13:27:44 +0200 Subject: feat(treesitter)!: add default fallback to `ft_to_lang` lookups Problem: Language names are only registered for filetype<->language lookups when parsers are actually loaded; this means users cannot rely on `vim.treesitter.language.get_lang()` or `get_filetypes()` to return the correct value when language and filetype coincide and always need to add explicit fallbacks. Solution: Always return the language name as valid filetype in `get_filetypes()`, and default to the filetype in `get_lang()`. Document this behavior. --- runtime/lua/vim/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index c21e0aff07..de52685220 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -95,7 +95,7 @@ function M.get_parser(bufnr, lang, opts) end if not valid_lang(lang) then - lang = M.language.get_lang(vim.bo[bufnr].filetype) or vim.bo[bufnr].filetype + lang = M.language.get_lang(vim.bo[bufnr].filetype) end if not valid_lang(lang) then -- cgit From b45c50f3140e7ece593f2126840900f5cc3d39ea Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 4 Oct 2024 02:13:31 -0700 Subject: docs: render `@since` versions, 0 means experimental #30649 An implication of this current approach is that `NVIM_API_LEVEL` should be bumped when a new Lua function is added. TODO(future): add a lint check which requires `@since` on all new functions. ref #25416 --- runtime/lua/vim/treesitter.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim/treesitter.lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index de52685220..ed7d31e1f7 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -447,6 +447,7 @@ end --- --- Can also be shown with `:InspectTree`. [:InspectTree]() --- +---@since 11 ---@param opts table|nil Optional options table with the following possible keys: --- - lang (string|nil): The language of the source buffer. If omitted, detect --- from the filetype of the source buffer. @@ -470,6 +471,7 @@ end --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' --- ``` --- +---@since 11 ---@param lnum integer|nil Line number to calculate fold level for ---@return string function M.foldexpr(lnum) -- cgit