aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lua/nvim-treesitter/caching.lua21
-rw-r--r--lua/nvim-treesitter/configs.lua62
-rw-r--r--lua/nvim-treesitter/fold.lua10
-rw-r--r--lua/nvim-treesitter/highlight.lua3
-rw-r--r--lua/nvim-treesitter/incremental_selection.lua26
-rw-r--r--lua/nvim-treesitter/indent.lua20
-rw-r--r--lua/nvim-treesitter/info.lua11
-rw-r--r--lua/nvim-treesitter/install.lua68
-rw-r--r--lua/nvim-treesitter/locals.lua76
-rw-r--r--lua/nvim-treesitter/parsers.lua11
-rw-r--r--lua/nvim-treesitter/query.lua86
-rw-r--r--lua/nvim-treesitter/query_predicates.lua51
-rw-r--r--lua/nvim-treesitter/shell_command_selectors.lua26
-rw-r--r--lua/nvim-treesitter/ts_utils.lua64
-rw-r--r--lua/nvim-treesitter/utils.lua72
-rwxr-xr-xscripts/update-readme.lua10
-rw-r--r--scripts/write-lockfile.lua5
17 files changed, 431 insertions, 191 deletions
diff --git a/lua/nvim-treesitter/caching.lua b/lua/nvim-treesitter/caching.lua
index 723dbde55..810d3a408 100644
--- a/lua/nvim-treesitter/caching.lua
+++ b/lua/nvim-treesitter/caching.lua
@@ -2,20 +2,26 @@ local api = vim.api
local M = {}
---- Creates a cache table for buffers keyed by a type name.
---- Cache entries attach to the buffer and cleanup entries
---- as buffers are detached.
+-- Creates a cache table for buffers keyed by a type name.
+-- Cache entries attach to the buffer and cleanup entries
+-- as buffers are detached.
function M.create_buffer_cache()
local cache = {}
+ ---@type table<string, table<string, any>>
local items = setmetatable({}, {
__index = function(tbl, key)
rawset(tbl, key, {})
return rawget(tbl, key)
end,
})
+
+ ---@type table<integer|string, boolean>
local loaded_buffers = {}
+ ---@param type_name string
+ ---@param bufnr integer
+ ---@param value any
function cache.set(type_name, bufnr, value)
if not loaded_buffers[bufnr] then
loaded_buffers[bufnr] = true
@@ -34,18 +40,27 @@ function M.create_buffer_cache()
items[tostring(bufnr)][type_name] = value
end
+ ---@param type_name string
+ ---@param bufnr integer
+ ---@return any
function cache.get(type_name, bufnr)
return items[tostring(bufnr)][type_name]
end
+ ---@param type_name string
+ ---@param bufnr integer
+ ---@return boolean
function cache.has(type_name, bufnr)
return cache.get(type_name, bufnr) ~= nil
end
+ ---@param type_name string
+ ---@param bufnr integer
function cache.remove(type_name, bufnr)
items[tostring(bufnr)][type_name] = nil
end
+ ---@param bufnr integer
function cache.clear_buffer(bufnr)
items[tostring(bufnr)] = nil
end
diff --git a/lua/nvim-treesitter/configs.lua b/lua/nvim-treesitter/configs.lua
index 87f930b8f..4207384ef 100644
--- a/lua/nvim-treesitter/configs.lua
+++ b/lua/nvim-treesitter/configs.lua
@@ -27,7 +27,9 @@ local config = {
update_strategy = "lockfile",
parser_install_dir = nil,
}
+
-- List of modules that need to be setup on initialization.
+---@type TSModule[][]
local queued_modules_defs = {}
-- Whether we've initialized the plugin yet.
local is_initialized = false
@@ -36,10 +38,12 @@ local is_initialized = false
---@field module_path string
---@field enable boolean|string[]|function(string): boolean
---@field disable boolean|string[]|function(string): boolean
+---@field keymaps table<string, string>
---@field is_supported function(string): boolean
---@field attach function(string)
---@field detach function(string)
---@field enabled_buffers table<integer, boolean>
+---@field additional_vim_regex_highlighting boolean|string[]
---@type {[string]: TSModule}
local builtin_modules = {
@@ -248,7 +252,7 @@ local function recurse_modules(accumulator, root, path)
end
end
----Shows current configuration of all nvim-treesitter modules
+-- Shows current configuration of all nvim-treesitter modules
---@param process_function function used as the `process` parameter
--- for vim.inspect (https://github.com/kikito/inspect.lua#optionsprocess)
local function config_info(process_function)
@@ -369,7 +373,7 @@ M.commands = {
---@param mod string module
---@param lang string the language of the buffer
----@param bufnr integer the bufnr
+---@param bufnr integer the buffer
function M.is_enabled(mod, lang, bufnr)
if not parsers.has_parser(lang) then
return false
@@ -438,8 +442,8 @@ function M.setup(user_data)
end, config.modules)
end
----Defines a table of modules that can be attached/detached to buffers
----based on language support. A module consist of the following properties:
+-- Defines a table of modules that can be attached/detached to buffers
+-- based on language support. A module consist of the following properties:
---* @enable Whether the modules is enabled. Can be true or false.
---* @disable A list of languages to disable the module for. Only relevant if enable is true.
---* @keymaps A list of user mappings for a given module if relevant.
@@ -451,9 +455,11 @@ end
--- if a `module_path` is not specified.
---* @detach A detach function that is called for each buffer that the module is enabled for. This is required
--- if a `module_path` is not specified.
----Modules are not setup until `init` is invoked by the plugin. This allows modules to be defined in any order
----and can be loaded lazily.
----@example
+--
+-- Modules are not setup until `init` is invoked by the plugin. This allows modules to be defined in any order
+-- and can be loaded lazily.
+--
+---* @example
---require"nvim-treesitter".define_modules {
--- my_cool_module = {
--- attach = function()
@@ -493,7 +499,7 @@ end
---Attaches a module to a buffer
---@param mod_name string the module name
----@param bufnr integer the bufnr
+---@param bufnr integer the buffer
---@param lang string the language of the buffer
function M.attach_module(mod_name, bufnr, lang)
bufnr = bufnr or api.nvim_get_current_buf()
@@ -506,9 +512,9 @@ function M.attach_module(mod_name, bufnr, lang)
end
end
----Detaches a module to a buffer
+-- Detaches a module to a buffer
---@param mod_name string the module name
----@param bufnr integer the bufnr
+---@param bufnr integer the buffer
function M.detach_module(mod_name, bufnr)
local resolved_mod = resolve_module(mod_name)
bufnr = bufnr or api.nvim_get_current_buf()
@@ -519,17 +525,18 @@ function M.detach_module(mod_name, bufnr)
end
end
----Same as attach_module, but if the module is already attached, detach it first.
+-- Same as attach_module, but if the module is already attached, detach it first.
---@param mod_name string the module name
----@param bufnr integer the bufnr
+---@param bufnr integer the buffer
---@param lang string the language of the buffer
function M.reattach_module(mod_name, bufnr, lang)
M.detach_module(mod_name, bufnr)
M.attach_module(mod_name, bufnr, lang)
end
----Gets available modules
+-- Gets available modules
---@param root {[string]:TSModule}|nil table to find modules
+---@return string[] modules list of module paths
function M.available_modules(root)
local modules = {}
@@ -542,24 +549,24 @@ end
---Gets a module config by path
---@param mod_path string path to the module
----@return TSModule|nil the module or nil
+---@return TSModule|nil: the module or nil
function M.get_module(mod_path)
local mod = utils.get_at_path(config.modules, mod_path)
return M.is_module(mod) and mod or nil
end
----Determines whether the provided table is a module.
----A module should contain an attach and detach function.
----@param mod table the module table
+-- Determines whether the provided table is a module.
+-- A module should contain an attach and detach function.
+---@param mod table|nil the module table
---@return boolean
function M.is_module(mod)
return type(mod) == "table"
and ((type(mod.attach) == "function" and type(mod.detach) == "function") or type(mod.module_path) == "string")
end
----Initializes built-in modules and any queued modules
----registered by plugins or the user.
+-- Initializes built-in modules and any queued modules
+-- registered by plugins or the user.
function M.init()
is_initialized = true
M.define_modules(builtin_modules)
@@ -569,22 +576,17 @@ function M.init()
end
end
----If parser_install_dir is not nil is used or created.
----If parser_install_dir is nil try the package dir of the nvim-treesitter
----plugin first, followed by the "site" dir from "runtimepath". "site" dir will
----be created if it doesn't exist. Using only the package dir won't work when
----the plugin is installed with Nix, since the "/nix/store" is read-only.
+-- If parser_install_dir is not nil is used or created.
+-- If parser_install_dir is nil try the package dir of the nvim-treesitter
+-- plugin first, followed by the "site" dir from "runtimepath". "site" dir will
+-- be created if it doesn't exist. Using only the package dir won't work when
+-- the plugin is installed with Nix, since the "/nix/store" is read-only.
---@param folder_name string|nil
---@return string|nil, string|nil
function M.get_parser_install_dir(folder_name)
folder_name = folder_name or "parser"
- local install_dir
- if config.parser_install_dir then
- install_dir = config.parser_install_dir
- else
- install_dir = utils.get_package_path()
- end
+ local install_dir = config.parser_install_dir or utils.get_package_path()
local parser_dir = utils.join_path(install_dir, folder_name)
return utils.create_or_reuse_writable_dir(
diff --git a/lua/nvim-treesitter/fold.lua b/lua/nvim-treesitter/fold.lua
index 50272622f..759599876 100644
--- a/lua/nvim-treesitter/fold.lua
+++ b/lua/nvim-treesitter/fold.lua
@@ -31,7 +31,10 @@ local folds_levels = tsutils.memoize_by_buf_tick(function(bufnr)
end)
-- start..stop is an inclusive range
+
+ ---@type table<number, number>
local start_counts = {}
+ ---@type table<number, number>
local stop_counts = {}
local prev_start = -1
@@ -40,11 +43,11 @@ local folds_levels = tsutils.memoize_by_buf_tick(function(bufnr)
local min_fold_lines = api.nvim_win_get_option(0, "foldminlines")
for _, match in ipairs(matches) do
- local start, stop, stop_col
+ local start, stop, stop_col ---@type integer, integer, integer
if match.metadata and match.metadata.range then
- start, _, stop, stop_col = unpack(match.metadata.range)
+ start, _, stop, stop_col = unpack(match.metadata.range) ---@type integer, integer, integer, integer
else
- start, _, stop, stop_col = match.node:range()
+ start, _, stop, stop_col = match.node:range() ---@type integer, integer, integer, integer
end
if stop_col == 0 then
@@ -65,6 +68,7 @@ local folds_levels = tsutils.memoize_by_buf_tick(function(bufnr)
end
end
+ ---@type string[]
local levels = {}
local current_level = 0
diff --git a/lua/nvim-treesitter/highlight.lua b/lua/nvim-treesitter/highlight.lua
index 042d326f9..5a3cc2e86 100644
--- a/lua/nvim-treesitter/highlight.lua
+++ b/lua/nvim-treesitter/highlight.lua
@@ -2,13 +2,14 @@ local configs = require "nvim-treesitter.configs"
local M = {}
----@param config table
+---@param config TSModule
---@param lang string
---@return boolean
local function should_enable_vim_regex(config, lang)
local additional_hl = config.additional_vim_regex_highlighting
local is_table = type(additional_hl) == "table"
+ ---@diagnostic disable-next-line: param-type-mismatch
return additional_hl and (not is_table or vim.tbl_contains(additional_hl, lang))
end
diff --git a/lua/nvim-treesitter/incremental_selection.lua b/lua/nvim-treesitter/incremental_selection.lua
index 4d4f2aad6..b0fd7b65f 100644
--- a/lua/nvim-treesitter/incremental_selection.lua
+++ b/lua/nvim-treesitter/incremental_selection.lua
@@ -8,6 +8,7 @@ local queries = require "nvim-treesitter.query"
local M = {}
+---@type table<integer, table<TSNode|nil>>
local selections = {}
function M.init_selection()
@@ -17,14 +18,15 @@ function M.init_selection()
ts_utils.update_selection(buf, node)
end
---- Get the range of the current visual selection.
+-- Get the range of the current visual selection.
--
--- The range start with 1 and the ending is inclusive.
+-- The range starts with 1 and the ending is inclusive.
+---@return integer, integer, integer, integer
local function visual_selection_range()
- local _, csrow, cscol, _ = unpack(vim.fn.getpos "'<")
- local _, cerow, cecol, _ = unpack(vim.fn.getpos "'>")
+ local _, csrow, cscol, _ = unpack(vim.fn.getpos "'<") ---@type integer, integer, integer, integer
+ local _, cerow, cecol, _ = unpack(vim.fn.getpos "'>") ---@type integer, integer, integer, integer
- local start_row, start_col, end_row, end_col
+ local start_row, start_col, end_row, end_col ---@type integer, integer, integer, integer
if csrow < cerow or (csrow == cerow and cscol <= cecol) then
start_row = csrow
@@ -41,12 +43,16 @@ local function visual_selection_range()
return start_row, start_col, end_row, end_col
end
+---@param node TSNode
+---@return boolean
local function range_matches(node)
local csrow, cscol, cerow, cecol = visual_selection_range()
local srow, scol, erow, ecol = ts_utils.get_vim_range { node:range() }
return srow == csrow and scol == cscol and erow == cerow and ecol == cecol
end
+---@param get_parent fun(node: TSNode): TSNode|nil
+---@return fun():nil
local function select_incremental(get_parent)
return function()
local buf = api.nvim_get_current_buf()
@@ -67,7 +73,7 @@ local function select_incremental(get_parent)
end
-- Find a node that changes the current selection.
- local node = nodes[#nodes]
+ local node = nodes[#nodes] ---@type TSNode
while true do
local parent = get_parent(node)
if not parent or parent == node then
@@ -116,7 +122,7 @@ function M.node_decremental()
end
table.remove(selections[buf])
- local node = nodes[#nodes]
+ local node = nodes[#nodes] ---@type TSNode
ts_utils.update_selection(buf, node)
end
@@ -127,14 +133,16 @@ local FUNCTION_DESCRIPTIONS = {
node_decremental = "Shrink selection to previous named node",
}
+---@param bufnr integer
function M.attach(bufnr)
local config = configs.get_module "incremental_selection"
for funcname, mapping in pairs(config.keymaps) do
if mapping then
- local mode
- local rhs
+ ---@type string, string|function
+ local mode, rhs
if funcname == "init_selection" then
mode = "n"
+ ---@type function
rhs = M[funcname]
else
mode = "x"
diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua
index a53d38c8d..c3e4c0888 100644
--- a/lua/nvim-treesitter/indent.lua
+++ b/lua/nvim-treesitter/indent.lua
@@ -15,16 +15,27 @@ M.comment_parsers = {
phpdoc = true,
}
+---@param root TSNode
+---@param lnum integer
+---@return TSNode
local function get_first_node_at_line(root, lnum)
local col = vim.fn.indent(lnum)
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
end
+---@param root TSNode
+---@param lnum integer
+---@return TSNode
local function get_last_node_at_line(root, lnum)
local col = #vim.fn.getline(lnum) - 1
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
end
+---@param bufnr integer
+---@param node TSNode
+---@param delimiter string
+---@return TSNode|nil child
+---@return boolean|nil is_end
local function find_delimiter(bufnr, node, delimiter)
for child, _ in node:iter_children() do
if child:type() == delimiter then
@@ -77,7 +88,7 @@ function M.get_indent(lnum)
end
-- Get language tree with smallest range around node that's not a comment parser
- local root, lang_tree
+ local root, lang_tree ---@type TSNode, LanguageTree
parser:for_each_tree(function(tstree, tree)
if not tstree or M.comment_parsers[tree:lang()] then
return
@@ -98,7 +109,7 @@ function M.get_indent(lnum)
local q = get_indents(vim.api.nvim_get_current_buf(), root, lang_tree:lang())
local is_empty_line = string.match(vim.fn.getline(lnum), "^%s*$") ~= nil
- local node
+ local node ---@type TSNode
if is_empty_line then
local prevlnum = vim.fn.prevnonblank(lnum)
node = get_last_node_at_line(root, prevlnum)
@@ -171,8 +182,9 @@ function M.get_indent(lnum)
-- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum)
if q.aligned_indent[node:id()] and srow ~= erow and (srow ~= lnum - 1) then
local metadata = q.aligned_indent[node:id()]
- local o_delim_node, is_last_in_line
+ local o_delim_node, is_last_in_line ---@type TSNode|nil, boolean|nil
if metadata.delimiter then
+ ---@type string
local opening_delimiter = metadata.delimiter and metadata.delimiter:sub(1, 1)
o_delim_node, is_last_in_line = find_delimiter(bufnr, node, opening_delimiter)
else
@@ -198,8 +210,10 @@ function M.get_indent(lnum)
return indent
end
+---@type table<integer, string>
local indent_funcs = {}
+---@param bufnr integer
function M.attach(bufnr)
indent_funcs[bufnr] = vim.bo.indentexpr
vim.bo.indentexpr = "nvim_treesitter#indent()"
diff --git a/lua/nvim-treesitter/info.lua b/lua/nvim-treesitter/info.lua
index eaeb9751c..6e94b357d 100644
--- a/lua/nvim-treesitter/info.lua
+++ b/lua/nvim-treesitter/info.lua
@@ -31,6 +31,8 @@ end
-- {'mod1', 'mod2.sub1', 'mod2.sub2', 'mod3'}
-- ->
-- { default = {'mod1', 'mod3'}, mod2 = {'sub1', 'sub2'}}
+---@param modulelist string[]
+---@return table
local function namespace_modules(modulelist)
local modules = {}
for _, module in ipairs(modulelist) do
@@ -50,6 +52,8 @@ local function namespace_modules(modulelist)
return modules
end
+---@param list string[]
+---@return integer length
local function longest_string_length(list)
local length = 0
for _, value in ipairs(list) do
@@ -60,6 +64,11 @@ local function longest_string_length(list)
return length
end
+---@param curbuf integer
+---@param origbuf integer
+---@param parserlist string[]
+---@param namespace string
+---@param modulelist string[]
local function append_module_table(curbuf, origbuf, parserlist, namespace, modulelist)
local maxlen_parser = longest_string_length(parserlist)
table.sort(modulelist)
@@ -104,6 +113,7 @@ local function print_info_modules(parserlist, module)
modules = namespace_modules(configs.available_modules())
end
+ ---@type string[]
local namespaces = {}
for k, _ in pairs(modules) do
table.insert(namespaces, k)
@@ -150,6 +160,7 @@ local function module_info(module)
end
end
+---@return string[]
function M.installed_parsers()
local installed = {}
for _, p in pairs(parsers.available_parsers()) do
diff --git a/lua/nvim-treesitter/install.lua b/lua/nvim-treesitter/install.lua
index 53efff5fd..6a72c6cef 100644
--- a/lua/nvim-treesitter/install.lua
+++ b/lua/nvim-treesitter/install.lua
@@ -9,6 +9,11 @@ local configs = require "nvim-treesitter.configs"
local shell = require "nvim-treesitter.shell_command_selectors"
local M = {}
+
+---@class LockfileInfo
+---@field revision string
+
+---@type table<string, LockfileInfo>
local lockfile = {}
M.compilers = { vim.fn.getenv "CC", "cc", "gcc", "clang", "cl", "zig" }
@@ -96,8 +101,8 @@ local function is_ignored_parser(lang)
return vim.tbl_contains(configs.get_ignored_parser_installs(), lang)
end
---- @param lang string
---- @return string|nil
+---@param lang string
+---@return string|nil
local function get_revision(lang)
if #lockfile == 0 then
load_lockfile()
@@ -123,8 +128,8 @@ local function get_installed_revision(lang)
end
-- Clean path for use in a prefix comparison
--- @param input string
--- @return string
+---@param input string
+---@return string
local function clean_path(input)
local pth = vim.fn.fnamemodify(input, ":p")
if fn.has "win32" == 1 then
@@ -133,7 +138,7 @@ local function clean_path(input)
return pth
end
----Checks if parser is installed with nvim-treesitter
+-- Checks if parser is installed with nvim-treesitter
---@param lang string
---@return boolean
local function is_installed(lang)
@@ -159,9 +164,9 @@ local function needs_update(lang)
return not revision or revision ~= get_installed_revision(lang)
end
----@return table
+---@return string[]
local function outdated_parsers()
- return vim.tbl_filter(function(lang)
+ return vim.tbl_filter(function(lang) ---@param lang string
return is_installed(lang) and needs_update(lang)
end, info.installed_parsers())
end
@@ -252,6 +257,8 @@ function M.iter_cmd(cmd_list, i, lang, success_message)
end
end
+---@param cmd Command
+---@return string command
local function get_command(cmd)
local options = ""
if cmd.opts and cmd.opts.args then
@@ -263,13 +270,15 @@ local function get_command(cmd)
end
end
- local final = string.format("%s %s", cmd.cmd, options)
+ local command = string.format("%s %s", cmd.cmd, options)
if cmd.opts and cmd.opts.cwd then
- final = shell.make_directory_change_for_command(cmd.opts.cwd, final)
+ command = shell.make_directory_change_for_command(cmd.opts.cwd, command)
end
- return final
+ return command
end
+---@param cmd_list Command[]
+---@return boolean
local function iter_cmd_sync(cmd_list)
for _, cmd in ipairs(cmd_list) do
if cmd.info then
@@ -311,7 +320,7 @@ local function run_install(cache_folder, install_folder, lang, repo, with_sync,
repo.url = maybe_local_path
end
- -- compile_location only needed for typescript installs.
+ ---@type string compile_location only needed for typescript installs.
local compile_location
if from_local_path then
compile_location = repo.url
@@ -355,7 +364,7 @@ local function run_install(cache_folder, install_folder, lang, repo, with_sync,
local cc = shell.select_executable(M.compilers)
if not cc then
api.nvim_err_writeln('No C compiler found! "' .. table.concat(
- vim.tbl_filter(function(c)
+ vim.tbl_filter(function(c) ---@param c string
return type(c) == "string"
end, M.compilers),
'", "'
@@ -368,6 +377,17 @@ local function run_install(cache_folder, install_folder, lang, repo, with_sync,
revision = get_revision(lang)
end
+ ---@class Command
+ ---@field cmd string
+ ---@field info string
+ ---@field err string
+ ---@field opts CmdOpts
+
+ ---@class CmdOpts
+ ---@field args string[]
+ ---@field cwd string
+
+ ---@type Command[]
local command_list = {}
if not from_local_path then
vim.list_extend(command_list, { shell.select_install_rm_cmd(cache_folder, project_name) })
@@ -411,7 +431,7 @@ local function run_install(cache_folder, install_folder, lang, repo, with_sync,
shell.select_mv_cmd("parser.so", parser_lib_name, compile_location),
{
cmd = function()
- vim.fn.writefile({ revision or "" }, utils.join_path(configs.get_parser_info_dir(), lang .. ".revision"))
+ vim.fn.writefile({ revision or "" }, utils.join_path(configs.get_parser_info_dir() or "", lang .. ".revision"))
end,
},
{ -- auto-attach modules after installation
@@ -432,7 +452,7 @@ local function run_install(cache_folder, install_folder, lang, repo, with_sync,
end
---@param lang string
----@param ask_reinstall boolean
+---@param ask_reinstall boolean|string
---@param cache_folder string
---@param install_folder string
---@param with_sync boolean
@@ -465,6 +485,14 @@ local function install_lang(lang, ask_reinstall, cache_folder, install_folder, w
run_install(cache_folder, install_folder, lang, install_info, with_sync, generate_from_grammar)
end
+---@class InstallOptions
+---@field with_sync boolean
+---@field ask_reinstall boolean|string
+---@field generate_from_grammar boolean
+---@field exclude_configured_parsers boolean
+
+-- Install a parser
+---@param options? InstallOptions
---@return function
local function install(options)
options = options or {}
@@ -491,8 +519,8 @@ local function install(options)
end
assert(install_folder)
- local languages
- local ask
+ local languages ---@type string[]
+ local ask ---@type boolean|string
if ... == "all" then
languages = parsers.available_parsers()
ask = false
@@ -533,6 +561,7 @@ function M.update(options)
M.lockfile = {}
reset_progress_counter()
if ... and ... ~= "all" then
+ ---@type string[]
local languages = vim.tbl_flatten { ... }
local installed = 0
for _, lang in ipairs(languages) do
@@ -578,6 +607,7 @@ function M.uninstall(...)
end
ensure_installed_parsers = utils.difference(ensure_installed_parsers, configs.get_ignored_parser_installs())
+ ---@type string[]
local languages = vim.tbl_flatten { ... }
for _, lang in ipairs(languages) do
local install_dir, err = configs.get_parser_install_dir()
@@ -634,7 +664,7 @@ function M.uninstall(...)
end
function M.write_lockfile(verbose, skip_langs)
- local sorted_parsers = {}
+ local sorted_parsers = {} ---@type Parser[]
-- Load previous lockfile
load_lockfile()
skip_langs = skip_langs or {}
@@ -643,6 +673,8 @@ function M.write_lockfile(verbose, skip_langs)
table.insert(sorted_parsers, { name = k, parser = v })
end
+ ---@param a Parser
+ ---@param b Parser
table.sort(sorted_parsers, function(a, b)
return a.name < b.name
end)
@@ -650,7 +682,7 @@ function M.write_lockfile(verbose, skip_langs)
for _, v in ipairs(sorted_parsers) do
if not vim.tbl_contains(skip_langs, v.name) then
-- I'm sure this can be done in aync way with iter_cmd
- local sha
+ local sha ---@type string
if v.parser.install_info.branch then
sha = vim.split(
vim.fn.systemlist(
diff --git a/lua/nvim-treesitter/locals.lua b/lua/nvim-treesitter/locals.lua
index dcfba2683..75ae07237 100644
--- a/lua/nvim-treesitter/locals.lua
+++ b/lua/nvim-treesitter/locals.lua
@@ -20,15 +20,16 @@ function M.iter_locals(bufnr, root)
return queries.iter_group_results(bufnr, "locals", root)
end
+---@param bufnr integer
+---@return any
function M.get_locals(bufnr)
return queries.get_matches(bufnr, "locals")
end
---- Creates unique id for a node based on text and range
--- @param scope: the scope node of the definition
--- @param bufnr: the buffer
--- @param node_text: the node text to use
--- @returns a string id
+-- Creates unique id for a node based on text and range
+---@param scope TSNode: the scope node of the definition
+---@param node_text string: the node text to use
+---@return string: a string id
function M.get_definition_id(scope, node_text)
-- Add a valid starting character in case node text doesn't start with a valid one.
return table.concat({ "k", node_text or "", scope:range() }, "_")
@@ -76,10 +77,13 @@ function M.get_references(bufnr)
return refs
end
---- Gets a table with all the scopes containing a node
+-- Gets a table with all the scopes containing a node
-- The order is from most specific to least (bottom up)
+---@param node TSNode
+---@param bufnr integer
+---@return TSNode[]
function M.get_scope_tree(node, bufnr)
- local scopes = {}
+ local scopes = {} ---@type TSNode[]
for scope in M.iter_scope_tree(node, bufnr) do
table.insert(scopes, scope)
@@ -88,7 +92,10 @@ function M.get_scope_tree(node, bufnr)
return scopes
end
---- Iterates over a nodes scopes moving from the bottom up
+-- Iterates over a nodes scopes moving from the bottom up
+---@param node TSNode
+---@param bufnr integer
+---@return fun(): TSNode|nil
function M.iter_scope_tree(node, bufnr)
local last_node = node
return function()
@@ -105,12 +112,12 @@ function M.iter_scope_tree(node, bufnr)
end
-- Gets a table of all nodes and their 'kinds' from a locals list
--- @param local_def the local list result
--- @returns a list of node entries
+---@param local_def any: the local list result
+---@return table: a list of node entries
function M.get_local_nodes(local_def)
local result = {}
- M.recurse_local_nodes(local_def, function(def, node, kind)
+ M.recurse_local_nodes(local_def, function(def, _node, kind)
table.insert(result, vim.tbl_extend("keep", { kind = kind }, def))
end)
@@ -123,10 +130,10 @@ end
-- * The node
-- * The full definition match `@definition.var.something` -> 'var.something'
-- * The last definition match `@definition.var.something` -> 'something'
--- @param The locals result
--- @param The accumulator function
--- @param The full match path to append to
--- @param The last match
+---@param local_def any The locals result
+---@param accumulator function The accumulator function
+---@param full_match? string The full match path to append to
+---@param last_match? string The last match
function M.recurse_local_nodes(local_def, accumulator, full_match, last_match)
if type(local_def) ~= "table" then
return
@@ -141,7 +148,7 @@ function M.recurse_local_nodes(local_def, accumulator, full_match, last_match)
end
end
---- Get a single dimension table to look definition nodes.
+-- Get a single dimension table to look definition nodes.
-- Keys are generated by using the range of the containing scope and the text of the definition node.
-- This makes looking up a definition for a given scope a simple key lookup.
--
@@ -152,8 +159,8 @@ end
-- Usage lookups require finding the definition of the node, so `find_definition`
-- is called very frequently, which is why this lookup must be fast as possible.
--
--- @param bufnr: the buffer
--- @returns a table for looking up definitions
+---@param bufnr integer: the buffer
+---@return table result: a table for looking up definitions
M.get_definitions_lookup_table = ts_utils.memoize_by_buf_tick(function(bufnr)
local definitions = M.get_definitions(bufnr)
local result = {}
@@ -173,19 +180,19 @@ M.get_definitions_lookup_table = ts_utils.memoize_by_buf_tick(function(bufnr)
return result
end)
---- Gets all the scopes of a definition based on the scope type
+-- Gets all the scopes of a definition based on the scope type
-- Scope types can be
--
-- "parent": Uses the parent of the containing scope, basically, skipping a scope
-- "global": Uses the top most scope
-- "local": Uses the containing scope of the definition. This is the default
--
--- @param node: the definition node
--- @param bufnr: the buffer
--- @param scope_type: the scope type
+---@param node TSNode: the definition node
+---@param bufnr integer: the buffer
+---@param scope_type string: the scope type
function M.get_definition_scopes(node, bufnr, scope_type)
local scopes = {}
- local scope_count = 1
+ local scope_count = 1 ---@type integer|nil
-- Definition is valid for the containing scope
-- and the containing scope of that scope
@@ -209,6 +216,11 @@ function M.get_definition_scopes(node, bufnr, scope_type)
return scopes
end
+---@param node TSNode
+---@param bufnr integer
+---@return TSNode node
+---@return TSNode scope
+---@return string|nil kind
function M.find_definition(node, bufnr)
local def_lookup = M.get_definitions_lookup_table(bufnr)
local node_text = ts_query.get_node_text(node, bufnr)
@@ -227,11 +239,11 @@ function M.find_definition(node, bufnr)
end
-- Finds usages of a node in a given scope.
--- @param node the node to find usages for
--- @param scope_node the node to look within
--- @returns a list of nodes
+---@param node TSNode the node to find usages for
+---@param scope_node TSNode the node to look within
+---@return TSNode[]: a list of nodes
function M.find_usages(node, scope_node, bufnr)
- local bufnr = bufnr or api.nvim_get_current_buf()
+ bufnr = bufnr or api.nvim_get_current_buf()
local node_text = ts_query.get_node_text(node, bufnr)
if not node_text or #node_text < 1 then
@@ -258,6 +270,10 @@ function M.find_usages(node, scope_node, bufnr)
return usages
end
+---@param node TSNode
+---@param bufnr? integer
+---@param allow_scope? boolean
+---@return TSNode|nil
function M.containing_scope(node, bufnr, allow_scope)
local bufnr = bufnr or api.nvim_get_current_buf()
local allow_scope = allow_scope == nil or allow_scope == true
@@ -284,8 +300,8 @@ function M.nested_scope(node, cursor_pos)
return
end
- local row = cursor_pos.row
- local col = cursor_pos.col
+ local row = cursor_pos.row ---@type integer
+ local col = cursor_pos.col ---@type integer
local scope = M.containing_scope(node)
for _, child in ipairs(ts_utils.get_named_children(scope)) do
@@ -321,6 +337,8 @@ function M.next_scope(node)
end
end
+---@param node TSNode
+---@return TSNode|nil
function M.previous_scope(node)
local bufnr = api.nvim_get_current_buf()
diff --git a/lua/nvim-treesitter/parsers.lua b/lua/nvim-treesitter/parsers.lua
index 7cd8c1e24..1813f08dd 100644
--- a/lua/nvim-treesitter/parsers.lua
+++ b/lua/nvim-treesitter/parsers.lua
@@ -36,6 +36,7 @@ local filetype_to_parsername = {
---@field filetype string
---@field maintainers string[]
---@field experimental boolean|nil
+---@field readme_name string|nil
---@type ParserInfo[]
local list = setmetatable({}, {
@@ -1542,11 +1543,13 @@ function M.ft_to_lang(ft)
end
end
+-- Get a list of all available parsers
+---@return string[]
function M.available_parsers()
if vim.fn.executable "tree-sitter" == 1 and vim.fn.executable "node" == 1 then
return vim.tbl_keys(M.list)
else
- return vim.tbl_filter(function(p)
+ return vim.tbl_filter(function(p) ---@param p string
return not M.list[p].install_info.requires_generate_from_grammar
end, vim.tbl_keys(M.list))
end
@@ -1598,9 +1601,9 @@ function M.get_tree_root(bufnr)
return M.get_parser(bufnr):parse()[1]:root()
end
--- get language of given buffer
--- @param optional buffer number or current buffer
--- @returns language string of buffer
+-- Gets the language of a given buffer
+---@param bufnr number? or current buffer
+---@return string
function M.get_buf_lang(bufnr)
bufnr = bufnr or api.nvim_get_current_buf()
return M.ft_to_lang(api.nvim_buf_get_option(bufnr, "ft"))
diff --git a/lua/nvim-treesitter/query.lua b/lua/nvim-treesitter/query.lua
index bc80d51e6..c7f1f952e 100644
--- a/lua/nvim-treesitter/query.lua
+++ b/lua/nvim-treesitter/query.lua
@@ -11,10 +11,10 @@ local EMPTY_ITER = function() end
M.built_in_query_groups = { "highlights", "locals", "folds", "indents", "injections" }
---- Creates a function that checks whether a given query exists
---- for a specific language.
+-- Creates a function that checks whether a given query exists
+-- for a specific language.
---@param query string
----@return function(string): boolean
+---@return fun(string): boolean
local function get_query_guard(query)
return function(lang)
return M.has_query_files(lang, query)
@@ -49,6 +49,9 @@ do
})
end
+ ---@param bufnr integer
+ ---@param query_group string
+ ---@return any
function M.get_matches(bufnr, query_group)
bufnr = bufnr or api.nvim_get_current_buf()
local cached_local = query_cache.get(query_group, bufnr)
@@ -94,9 +97,10 @@ do
end
-- cache will auto set the table for each lang if it is nil
+ ---@type table<string, table<string, Query>>
local cache = setmetatable({}, mt)
- --- Same as `vim.treesitter.query` except will return cached values
+ -- Same as `vim.treesitter.query` except will return cached values
---@param lang string
---@param query_name string
function M.get_query(lang, query_name)
@@ -107,12 +111,13 @@ do
return cache[lang][query_name]
end
- --- Invalidates the query file cache.
- --- If lang and query_name is both present, will reload for only the lang and query_name.
- --- If only lang is present, will reload all query_names for that lang
- --- If none are present, will reload everything
- ---@param lang string
- ---@param query_name string
+ -- Invalidates the query file cache.
+ --
+ -- If lang and query_name is both present, will reload for only the lang and query_name.
+ -- If only lang is present, will reload all query_names for that lang
+ -- If none are present, will reload everything
+ ---@param lang? string
+ ---@param query_name? string
function M.invalidate_query_cache(lang, query_name)
if lang and query_name then
cache[lang][query_name] = nil
@@ -137,7 +142,7 @@ do
end
end
---- This function is meant for an autocommand and not to be used. Only use if file is a query file.
+-- This function is meant for an autocommand and not to be used. Only use if file is a query file.
---@param fname string
function M.invalidate_query_file(fname)
local fnamemodify = vim.fn.fnamemodify
@@ -145,14 +150,14 @@ function M.invalidate_query_file(fname)
end
---@class QueryInfo
----@field root LanguageTree
+---@field root TSNode
---@field source integer
---@field start integer
---@field stop integer
---@param bufnr integer
---@param query_name string
----@param root LanguageTree
+---@param root TSNode
---@param root_lang string|nil
---@return Query|nil, QueryInfo|nil
local function prepare_query(bufnr, query_name, root, root_lang)
@@ -214,15 +219,21 @@ end
---@param end_row integer
function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row)
-- A function that splits a string on '.'
- local function split(string)
+ ---@param to_split string
+ ---@return string[]
+ local function split(to_split)
local t = {}
- for str in string.gmatch(string, "([^.]+)") do
+ for str in string.gmatch(to_split, "([^.]+)") do
table.insert(t, str)
end
return t
end
+
-- Given a path (i.e. a List(String)) this functions inserts value at path
+ ---@param object any
+ ---@param path string[]
+ ---@param value any
local function insert_to_path(object, path, value)
local curr_obj = object
@@ -256,6 +267,7 @@ function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row)
end
-- Add some predicates for testing
+ ---@type string[][] ( TODO: make pred type so this can be pred[])
local preds = query.info.patterns[pattern]
if preds then
for _, pred in pairs(preds) do
@@ -279,22 +291,22 @@ function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row)
return iterator
end
---- Return all nodes corresponding to a specific capture path (like @definition.var, @reference.type)
----Works like M.get_references or M.get_scopes except you can choose the capture
----Can also be a nested capture like @definition.function to get all nodes defining a function.
----
+-- Return all nodes corresponding to a specific capture path (like @definition.var, @reference.type)
+-- Works like M.get_references or M.get_scopes except you can choose the capture
+-- Can also be a nested capture like @definition.function to get all nodes defining a function.
+--
---@param bufnr integer the buffer
---@param captures string|string[]
---@param query_group string the name of query group (highlights or injections for example)
----@param root LanguageTree|nil node from where to start the search
+---@param root TSNode|nil node from where to start the search
---@param lang string|nil the language from where to get the captures.
---- Root nodes can have several languages.
+--- Root nodes can have several languages.
---@return table|nil
function M.get_capture_matches(bufnr, captures, query_group, root, lang)
if type(captures) == "string" then
captures = { captures }
end
- local strip_captures = {}
+ local strip_captures = {} ---@type string[]
for i, capture in ipairs(captures) do
if capture:sub(1, 1) ~= "@" then
error 'Captures must start with "@"'
@@ -342,14 +354,21 @@ function M.iter_captures(bufnr, query_name, root, lang)
return wrapped_iter
end
+---@param bufnr integer
+---@param capture_string string
+---@param query_group string
+---@param filter_predicate fun(match: table): boolean
+---@param scoring_function fun(match: table): number
+---@param root TSNode
+---@return table|unknown
function M.find_best_match(bufnr, capture_string, query_group, filter_predicate, scoring_function, root)
if string.sub(capture_string, 1, 1) == "@" then
--remove leading "@"
capture_string = string.sub(capture_string, 2)
end
- local best
- local best_score
+ local best ---@type table|nil
+ local best_score ---@type number
for maybe_match in M.iter_group_results(bufnr, query_group, root) do
local match = utils.get_at_path(maybe_match, capture_string)
@@ -372,8 +391,8 @@ end
---Iterates matches from a query file.
---@param bufnr integer the buffer
---@param query_group string the query file to use
----@param root LanguageTree the root node
----@param root_lang string|nil the root node lang, if known
+---@param root TSNode the root node
+---@param root_lang? string the root node lang, if known
function M.iter_group_results(bufnr, query_group, root, root_lang)
local query, params = prepare_query(bufnr, query_group, root, root_lang)
if not query then
@@ -396,13 +415,14 @@ end
---@alias CaptureResFn function(string, LanguageTree, LanguageTree): string, string
---- Same as get_capture_matches except this will recursively get matches for every language in the tree.
----@param bufnr integer The bufnr
+-- Same as get_capture_matches except this will recursively get matches for every language in the tree.
+---@param bufnr integer The buffer
---@param capture_or_fn string|CaptureResFn The capture to get. If a function is provided then that
---- function will be used to resolve both the capture and query argument.
---- The function can return `nil` to ignore that tree.
----@param query_type string The query to get the capture from. This is ignore if a function is provided
---- for the captuer argument.
+--- function will be used to resolve both the capture and query argument.
+--- The function can return `nil` to ignore that tree.
+---@param query_type string? The query to get the capture from. This is ignored if a function is provided
+--- for the capture argument.
+---@return table[]
function M.get_capture_matches_recursively(bufnr, capture_or_fn, query_type)
---@type CaptureResFn
local type_fn
@@ -422,7 +442,7 @@ function M.get_capture_matches_recursively(bufnr, capture_or_fn, query_type)
local capture, type_ = type_fn(lang, tree, lang_tree)
if capture then
- vim.list_extend(matches, M.get_capture_matches(bufnr, capture, type_, tree:root(), lang))
+ vim.list_extend(matches, M.get_capture_matches(bufnr, capture, type_, tree:root(), lang) or {})
end
end)
end
diff --git a/lua/nvim-treesitter/query_predicates.lua b/lua/nvim-treesitter/query_predicates.lua
index 0638b6e79..2509a677c 100644
--- a/lua/nvim-treesitter/query_predicates.lua
+++ b/lua/nvim-treesitter/query_predicates.lua
@@ -20,12 +20,17 @@ local function valid_args(name, pred, count, strict_count)
return true
end
-query.add_predicate("nth?", function(match, pattern, bufnr, pred)
+---@param match (TSNode|nil)[]
+---@param _pattern string
+---@param _bufnr integer
+---@param pred string[]
+---@return boolean|nil
+query.add_predicate("nth?", function(match, _pattern, _bufnr, pred)
if not valid_args("nth?", pred, 2, true) then
return
end
- local node = match[pred[2]]
+ local node = match[pred[2]] ---@type TSNode
local n = tonumber(pred[3])
if node and node:parent() and node:parent():named_child_count() > n then
return node:parent():named_child(n) == node
@@ -34,7 +39,12 @@ query.add_predicate("nth?", function(match, pattern, bufnr, pred)
return false
end)
-local function has_ancestor(match, pattern, bufnr, pred)
+---@param match (TSNode|nil)[]
+---@param _pattern string
+---@param _bufnr integer
+---@param pred string[]
+---@return boolean|nil
+local function has_ancestor(match, _pattern, _bufnr, pred)
if not valid_args(pred[1], pred, 2) then
return
end
@@ -65,7 +75,12 @@ query.add_predicate("has-ancestor?", has_ancestor)
query.add_predicate("has-parent?", has_ancestor)
-query.add_predicate("is?", function(match, pattern, bufnr, pred)
+---@param match (TSNode|nil)[]
+---@param _pattern string
+---@param bufnr integer
+---@param pred string[]
+---@return boolean|nil
+query.add_predicate("is?", function(match, _pattern, bufnr, pred)
if not valid_args("is?", pred, 2) then
return
end
@@ -84,7 +99,12 @@ query.add_predicate("is?", function(match, pattern, bufnr, pred)
return vim.tbl_contains(types, kind)
end)
-query.add_predicate("has-type?", function(match, pattern, bufnr, pred)
+---@param match (TSNode|nil)[]
+---@param _pattern string
+---@param _bufnr integer
+---@param pred string[]
+---@return boolean|nil
+query.add_predicate("has-type?", function(match, _pattern, _bufnr, pred)
if not valid_args(pred[1], pred, 2) then
return
end
@@ -102,8 +122,14 @@ end)
-- Just avoid some annoying warnings for this directive
query.add_directive("make-range!", function() end)
+---@param match (TSNode|nil)[]
+---@param _ string
+---@param bufnr integer
+---@param pred string[]
+---@param metadata table
+---@return boolean|nil
query.add_directive("downcase!", function(match, _, bufnr, pred, metadata)
- local text, key, value
+ local text, key, value ---@type string|string[], string, string|integer
if #pred == 3 then
-- (#downcase! @capture "key")
@@ -129,13 +155,19 @@ query.add_directive("downcase!", function(match, _, bufnr, pred, metadata)
end
end)
+---@param match (TSNode|nil)[]
+---@param _pattern string
+---@param _bufnr integer
+---@param pred string[]
+---@param metadata table
+---@return boolean|nil
query.add_directive("exclude_children!", function(match, _pattern, _bufnr, pred, metadata)
local capture_id = pred[2]
local node = match[capture_id]
local start_row, start_col, end_row, end_col = node:range()
local ranges = {}
for i = 0, node:named_child_count() - 1 do
- local child = node:named_child(i)
+ local child = node:named_child(i) ---@type TSNode
local child_start_row, child_start_col, child_end_row, child_end_col = child:range()
if child_start_row > start_row or child_start_col > start_col then
table.insert(ranges, {
@@ -156,6 +188,11 @@ end)
-- Trim blank lines from end of the region
-- Arguments are the captures to trim.
+---@param match (TSNode|nil)[]
+---@param _ string
+---@param bufnr integer
+---@param pred string[]
+---@param metadata table
query.add_directive("trim!", function(match, _, bufnr, pred, metadata)
for _, id in ipairs { select(2, unpack(pred)) } do
local node = match[id]
diff --git a/lua/nvim-treesitter/shell_command_selectors.lua b/lua/nvim-treesitter/shell_command_selectors.lua
index 9676beb5d..7a0610a98 100644
--- a/lua/nvim-treesitter/shell_command_selectors.lua
+++ b/lua/nvim-treesitter/shell_command_selectors.lua
@@ -3,6 +3,8 @@ local utils = require "nvim-treesitter.utils"
-- Convert path for cmd.exe on Windows.
-- This is needed when vim.opt.shellslash is in use.
+---@param p string
+---@return string
local function cmdpath(p)
if vim.opt.shellslash:get() then
local r = p:gsub("/", "\\")
@@ -14,6 +16,10 @@ end
local M = {}
+---@param directory string
+---@param cwd string
+---@param info_msg string
+---@return table
function M.select_mkdir_cmd(directory, cwd, info_msg)
if fn.has "win32" == 1 then
return {
@@ -38,6 +44,9 @@ function M.select_mkdir_cmd(directory, cwd, info_msg)
end
end
+---@param file string
+---@param info_msg string
+---@return table
function M.select_rm_file_cmd(file, info_msg)
if fn.has "win32" == 1 then
return {
@@ -60,13 +69,17 @@ function M.select_rm_file_cmd(file, info_msg)
end
end
+---@param executables string[]
---@return string|nil
function M.select_executable(executables)
- return vim.tbl_filter(function(c)
+ return vim.tbl_filter(function(c) ---@param c string
return c ~= vim.NIL and fn.executable(c) == 1
end, executables)[1]
end
+---@param repo InstallInfo
+---@param compiler string
+---@return string[]
function M.select_compiler_args(repo, compiler)
if string.match(compiler, "cl$") or string.match(compiler, "cl.exe$") then
return {
@@ -98,7 +111,7 @@ function M.select_compiler_args(repo, compiler)
"-Os",
}
if
- #vim.tbl_filter(function(file)
+ #vim.tbl_filter(function(file) ---@param file string
local ext = vim.fn.fnamemodify(file, ":e")
return ext == "cc" or ext == "cpp" or ext == "cxx"
end, repo.files) > 0
@@ -186,6 +199,12 @@ function M.select_mv_cmd(from, to, cwd)
end
end
+---@param repo InstallInfo
+---@param project_name string
+---@param cache_folder string
+---@param revision string|nil
+---@param prefer_git boolean
+---@return table
function M.select_download_commands(repo, project_name, cache_folder, revision, prefer_git)
local can_use_tar = vim.fn.executable "tar" == 1 and vim.fn.executable "curl" == 1
local is_github = repo.url:find("github.com", 1, true)
@@ -277,6 +296,9 @@ function M.select_download_commands(repo, project_name, cache_folder, revision,
end
end
+---@param dir string
+---@param command string
+---@return string command
function M.make_directory_change_for_command(dir, command)
if fn.has "win32" == 1 then
return string.format("pushd %s & %s & popd", cmdpath(dir), command)
diff --git a/lua/nvim-treesitter/ts_utils.lua b/lua/nvim-treesitter/ts_utils.lua
index 31dfbf048..6b330fda4 100644
--- a/lua/nvim-treesitter/ts_utils.lua
+++ b/lua/nvim-treesitter/ts_utils.lua
@@ -34,6 +34,11 @@ local function get_node_text(node, bufnr)
end
---@private
+---@param node TSNode
+---@param type_patterns string[]
+---@param transform_fn fun(line: string): string
+---@param bufnr integer
+---@return string
function M._get_line_for_node(node, type_patterns, transform_fn, bufnr)
local node_type = node:type()
local is_valid = false
@@ -51,7 +56,7 @@ function M._get_line_for_node(node, type_patterns, transform_fn, bufnr)
return line:gsub("%%", "%%%%")
end
---- Gets the actual text content of a node
+-- Gets the actual text content of a node
-- @deprecated Use vim.treesitter.query.get_node_text
-- @param node the node to get the text from
-- @param bufnr the buffer containing the node
@@ -64,7 +69,7 @@ function M.get_node_text(node, bufnr)
return get_node_text(node, bufnr)
end
---- Determines whether a node is the parent of another
+-- Determines whether a node is the parent of another
-- @param dest the possible parent
-- @param source the possible child node
function M.is_parent(dest, source)
@@ -84,12 +89,12 @@ function M.is_parent(dest, source)
return false
end
---- Get next node with same parent
--- @param node node
--- @param allow_switch_parents allow switching parents if last node
--- @param allow_next_parent allow next parent if last node and next parent without children
+-- Get next node with same parent
+---@param node TSNode
+---@param allow_switch_parents? boolean allow switching parents if last node
+---@param allow_next_parent? boolean allow next parent if last node and next parent without children
function M.get_next_node(node, allow_switch_parents, allow_next_parent)
- local destination_node
+ local destination_node ---@type TSNode
local parent = node:parent()
if not parent then
@@ -116,12 +121,12 @@ function M.get_next_node(node, allow_switch_parents, allow_next_parent)
return destination_node
end
---- Get previous node with same parent
--- @param node node
--- @param allow_switch_parents allow switching parents if first node
--- @param allow_previous_parent allow previous parent if first node and previous parent without children
+-- Get previous node with same parent
+---@param node TSNode
+---@param allow_switch_parents? boolean allow switching parents if first node
+---@param allow_previous_parent? boolean allow previous parent if first node and previous parent without children
function M.get_previous_node(node, allow_switch_parents, allow_previous_parent)
- local destination_node
+ local destination_node ---@type TSNode
local parent = node:parent()
if not parent then
return
@@ -148,7 +153,7 @@ function M.get_previous_node(node, allow_switch_parents, allow_previous_parent)
end
function M.get_named_children(node)
- local nodes = {}
+ local nodes = {} ---@type TSNode[]
for i = 0, node:named_child_count() - 1, 1 do
nodes[i + 1] = node:named_child(i)
end
@@ -166,7 +171,7 @@ function M.get_node_at_cursor(winnr, ignore_injected_langs)
return
end
- local root
+ local root ---@type TSNode|nil
if ignore_injected_langs then
for _, tree in ipairs(root_lang_tree:trees()) do
local tree_root = tree:root()
@@ -209,6 +214,9 @@ function M.get_root_for_position(line, col, root_lang_tree)
return nil, nil, lang_tree
end
+---comment
+---@param node TSNode
+---@return TSNode result
function M.get_root_for_node(node)
local parent = node
local result = node
@@ -228,12 +236,17 @@ function M.highlight_node(node, buf, hl_namespace, hl_group)
M.highlight_range({ node:range() }, buf, hl_namespace, hl_group)
end
---- Get a compatible vim range (1 index based) from a TS node range.
+-- Get a compatible vim range (1 index based) from a TS node range.
--
-- TS nodes start with 0 and the end col is ending exclusive.
-- They also treat a EOF/EOL char as a char ending in the first
-- col of the next row.
+---comment
+---@param range integer[]
+---@param buf integer|nil
+---@return integer, integer, integer, integer
function M.get_vim_range(range, buf)
+ ---@type integer, integer, integer, integer
local srow, scol, erow, ecol = unpack(range)
srow = srow + 1
scol = scol + 1
@@ -253,7 +266,9 @@ function M.get_vim_range(range, buf)
end
function M.highlight_range(range, buf, hl_namespace, hl_group)
+ ---@type integer, integer, integer, integer
local start_row, start_col, end_row, end_col = unpack(range)
+ ---@diagnostic disable-next-line: missing-parameter
vim.highlight.range(buf, hl_namespace, hl_group, { start_row, start_col }, { end_row, end_col })
end
@@ -288,13 +303,15 @@ function M.update_selection(buf, node, selection_mode)
end
-- Byte length of node range
+---@param node TSNode
+---@return number
function M.node_length(node)
local _, _, start_byte = node:start()
local _, _, end_byte = node:end_()
return end_byte - start_byte
end
---- @deprecated Use `vim.treesitter.is_in_node_range()` instead
+---@deprecated Use `vim.treesitter.is_in_node_range()` instead
function M.is_in_node_range(node, line, col)
vim.notify_once(
"nvim-treesitter.ts_utils.is_in_node_range is deprecated: use vim.treesitter.is_in_node_range",
@@ -303,7 +320,7 @@ function M.is_in_node_range(node, line, col)
return ts.is_in_node_range(node, line, col)
end
---- @deprecated Use `vim.treesitter.get_node_range()` instead
+---@deprecated Use `vim.treesitter.get_node_range()` instead
function M.get_node_range(node_or_range)
vim.notify_once(
"nvim-treesitter.ts_utils.get_node_range is deprecated: use vim.treesitter.get_node_range",
@@ -312,6 +329,8 @@ function M.get_node_range(node_or_range)
return ts.get_node_range(node_or_range)
end
+---@param node TSNode
+---@return table
function M.node_to_lsp_range(node)
local start_line, start_col, end_line, end_col = ts.get_node_range(node)
local rtn = {}
@@ -320,16 +339,18 @@ function M.node_to_lsp_range(node)
return rtn
end
---- Memoizes a function based on the buffer tick of the provided bufnr.
+-- Memoizes a function based on the buffer tick of the provided bufnr.
-- The cache entry is cleared when the buffer is detached to avoid memory leaks.
--- @param fn: the fn to memoize, taking the bufnr as first argument
--- @param options:
+-- The options argument is a table with two optional values:
-- - bufnr: extracts a bufnr from the given arguments.
-- - key: extracts the cache key from the given arguments.
--- @returns a memoized function
+---@param fn function the fn to memoize, taking the buffer as first argument
+---@param options? {bufnr: integer?, key: string|fun(...): string?} the memoization options
+---@return function: a memoized function
function M.memoize_by_buf_tick(fn, options)
options = options or {}
+ ---@type table<string, {result: any, last_tick: integer}>
local cache = {}
local bufnr_fn = utils.to_func(options.bufnr or utils.identity)
local key_fn = utils.to_func(options.key or utils.identity)
@@ -424,6 +445,7 @@ function M.goto_node(node, goto_end, avoid_set_jump)
utils.set_jump()
end
local range = { M.get_vim_range { node:range() } }
+ ---@type table<number>
local position
if not goto_end then
position = { range[1], range[2] }
diff --git a/lua/nvim-treesitter/utils.lua b/lua/nvim-treesitter/utils.lua
index 9338be874..4aa8e5984 100644
--- a/lua/nvim-treesitter/utils.lua
+++ b/lua/nvim-treesitter/utils.lua
@@ -5,12 +5,15 @@ local luv = vim.loop
local M = {}
-- Wrapper around vim.notify with common options set.
+---@param msg string
+---@param log_level number|nil
+---@param opts table|nil
function M.notify(msg, log_level, opts)
local default_opts = { title = "nvim-treesitter" }
vim.notify(msg, log_level, vim.tbl_extend("force", default_opts, opts or {}))
end
--- Returns the system specific path separator.
+-- Returns the system-specific path separator.
---@return string
function M.get_path_sep()
return (fn.has "win32" == 1 and not vim.opt.shellslash:get()) and "\\" or "/"
@@ -18,10 +21,13 @@ end
-- Returns a function that joins the given arguments with separator. Arguments
-- can't be nil. Example:
+--
--[[
-print(M.generate_join(" ")("foo", "bar"))
+ print(M.generate_join(" ")("foo", "bar"))
--]]
--- prints "foo bar"
+--prints "foo bar"
+---@param separator string
+---@return fun(...: string): string
function M.generate_join(separator)
return function(...)
return table.concat({ ... }, separator)
@@ -32,13 +38,18 @@ M.join_path = M.generate_join(M.get_path_sep())
M.join_space = M.generate_join " "
---- Define user defined vim command which calls nvim-treesitter module function
---- - If module name is 'mod', it should be defined in hierarchy 'nvim-treesitter.mod'
---- - A table with name 'commands' should be defined in 'mod' which needs to be passed as
---- the commands param of this function
----
----@param mod string, Name of the module that resides in the hierarchy - nvim-treesitter.module
----@param commands table, Command list for the module
+---@class Command
+---@field run function
+---@field f_args string
+---@field args string
+
+-- Define user defined vim command which calls nvim-treesitter module function
+-- - If module name is 'mod', it should be defined in hierarchy 'nvim-treesitter.mod'
+-- - A table with name 'commands' should be defined in 'mod' which needs to be passed as
+-- the commands param of this function
+--
+---@param mod string Name of the module that resides in the hierarchy - nvim-treesitter.module
+---@param commands table<string, Command> Command list for the module
--- - {command_name} Name of the vim user defined command, Keys:
--- - {run}: (function) callback function that needs to be executed
--- - {f_args}: (string, default <f-args>)
@@ -46,7 +57,7 @@ M.join_space = M.generate_join " "
--- - {args}: (string, optional)
--- - vim command attributes
---
----Example:
+---* @example
--- If module is nvim-treesitter.custom_mod
--- <pre>
--- M.commands = {
@@ -136,18 +147,22 @@ function M.get_site_dir()
end
-- Gets a property at path
--- @param tbl the table to access
--- @param path the '.' separated path
--- @returns the value at path or nil
+---@param tbl table the table to access
+---@param path string the '.' separated path
+---@return table|nil result the value at path or nil
function M.get_at_path(tbl, path)
if path == "" then
return tbl
end
+
local segments = vim.split(path, ".", true)
+ ---@type table[]|table
local result = tbl
for _, segment in ipairs(segments) do
if type(result) == "table" then
+ ---@type table
+ -- TODO: figure out the actual type of tbl
result = result[segment]
end
end
@@ -159,17 +174,9 @@ function M.set_jump()
vim.cmd "normal! m'"
end
-function M.index_of(tbl, obj)
- for i, o in ipairs(tbl) do
- if o == obj then
- return i
- end
- end
-end
-
-- Filters a list based on the given predicate
--- @param tbl The list to filter
--- @param predicate The predicate to filter with
+---@param tbl any[] The list to filter
+---@param predicate fun(v:any, i:number):boolean The predicate to filter with
function M.filter(tbl, predicate)
local result = {}
@@ -184,8 +191,9 @@ end
-- Returns a list of all values from the first list
-- that are not present in the second list.
--- @params tbl1 The first table
--- @params tbl2 The second table
+---@param tbl1 any[] The first table
+---@param tbl2 any[] The second table
+---@return table
function M.difference(tbl1, tbl2)
return M.filter(tbl1, function(v)
return not vim.tbl_contains(tbl2, v)
@@ -196,12 +204,19 @@ function M.identity(a)
return a
end
+-- Returns a function returning the given value
+---@param a any
+---@return fun():any
function M.constant(a)
return function()
return a
end
end
+-- Returns a function that returns the given value if it is a function,
+-- otherwise returns a function that returns the given value.
+---@param a any
+---@return fun(...):any
function M.to_func(a)
return type(a) == "function" and a or M.constant(a)
end
@@ -209,7 +224,10 @@ end
---@return string|nil
function M.ts_cli_version()
if fn.executable "tree-sitter" == 1 then
- local handle = io.popen "tree-sitter -V"
+ local handle = io.popen "tree-sitter -V"
+ if not handle then
+ return
+ end
local result = handle:read "*a"
handle:close()
return vim.split(result, "\n")[1]:match "[^tree%psitter ].*"
diff --git a/scripts/update-readme.lua b/scripts/update-readme.lua
index c2a1eac9f..d21628e2c 100755
--- a/scripts/update-readme.lua
+++ b/scripts/update-readme.lua
@@ -1,4 +1,9 @@
-- Execute as `nvim --headless -c "luafile ./scripts/update-readme.lua"`
+
+---@class Parser
+---@field name string
+---@field parser ParserInfo
+
local parsers = require("nvim-treesitter.parsers").get_parser_configs()
local sorted_parsers = {}
@@ -6,12 +11,15 @@ for k, v in pairs(parsers) do
table.insert(sorted_parsers, { name = k, parser = v })
end
+---@param a Parser
+---@param b Parser
table.sort(sorted_parsers, function(a, b)
return a.name < b.name
end)
local generated_text = ""
+---@param v Parser
for _, v in ipairs(sorted_parsers) do
local link = "[" .. (v.parser.readme_name or v.name) .. "](" .. v.parser.install_info.url .. ")"
@@ -41,7 +49,7 @@ local new_readme_text = string.gsub(
)
vim.fn.writefile(vim.fn.split(new_readme_text, "\n"), "README.md")
-if string.find(readme_text, generated_text, 1, "plain") then
+if string.find(readme_text, generated_text, 1, true) then
print "README.md is up-to-date!"
vim.cmd "q"
else
diff --git a/scripts/write-lockfile.lua b/scripts/write-lockfile.lua
index 6658c8859..b8833b91e 100644
--- a/scripts/write-lockfile.lua
+++ b/scripts/write-lockfile.lua
@@ -1,10 +1,15 @@
-- Execute as `nvim --headless -c "luafile ./scripts/write-lockfile.lua"`
+
+---@type string|any[]
local skip_langs = vim.fn.getenv "SKIP_LOCKFILE_UPDATE_FOR_LANGS"
+
if skip_langs == vim.NIL then
skip_langs = {}
else
+ ---@diagnostic disable-next-line: param-type-mismatch
skip_langs = vim.fn.split(skip_langs, ",")
end
+
print("Skipping languages: " .. vim.inspect(skip_langs))
require("nvim-treesitter.install").write_lockfile("verbose", skip_langs)
vim.cmd "q"