diff options
| -rw-r--r-- | lua/nvim-treesitter/caching.lua | 21 | ||||
| -rw-r--r-- | lua/nvim-treesitter/configs.lua | 62 | ||||
| -rw-r--r-- | lua/nvim-treesitter/fold.lua | 10 | ||||
| -rw-r--r-- | lua/nvim-treesitter/highlight.lua | 3 | ||||
| -rw-r--r-- | lua/nvim-treesitter/incremental_selection.lua | 26 | ||||
| -rw-r--r-- | lua/nvim-treesitter/indent.lua | 20 | ||||
| -rw-r--r-- | lua/nvim-treesitter/info.lua | 11 | ||||
| -rw-r--r-- | lua/nvim-treesitter/install.lua | 68 | ||||
| -rw-r--r-- | lua/nvim-treesitter/locals.lua | 76 | ||||
| -rw-r--r-- | lua/nvim-treesitter/parsers.lua | 11 | ||||
| -rw-r--r-- | lua/nvim-treesitter/query.lua | 86 | ||||
| -rw-r--r-- | lua/nvim-treesitter/query_predicates.lua | 51 | ||||
| -rw-r--r-- | lua/nvim-treesitter/shell_command_selectors.lua | 26 | ||||
| -rw-r--r-- | lua/nvim-treesitter/ts_utils.lua | 64 | ||||
| -rw-r--r-- | lua/nvim-treesitter/utils.lua | 72 | ||||
| -rwxr-xr-x | scripts/update-readme.lua | 10 | ||||
| -rw-r--r-- | scripts/write-lockfile.lua | 5 |
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" |
