diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/_defaults.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api.lua | 55 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 28 | ||||
-rw-r--r-- | runtime/lua/vim/health.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 136 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/client.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/health.lua | 42 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/inlay_hint.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/provider/health.lua | 29 | ||||
-rw-r--r-- | runtime/lua/vim/provider/python.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_headings.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_meta/misc.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 123 |
13 files changed, 269 insertions, 187 deletions
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 7440af1a96..3cb38be450 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -622,10 +622,10 @@ do end vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' - vim.keymap.set('n', '[[', function() + vim.keymap.set({ 'n', 'x' }, '[[', function() jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, -vim.v.count1) end, { buffer = args.buf, desc = 'Jump [count] shell prompts backward' }) - vim.keymap.set('n', ']]', function() + vim.keymap.set({ 'n', 'x' }, ']]', function() jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, vim.v.count1) end, { buffer = args.buf, desc = 'Jump [count] shell prompts forward' }) end, diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index e7ad91132d..62df0a7707 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -939,8 +939,8 @@ function vim.api.nvim_create_augroup(name, opts) end --- - desc (string) optional: description (for documentation and troubleshooting). --- - callback (function|string) optional: Lua function (or Vimscript function name, if --- string) called when the event(s) is triggered. Lua callback can return a truthy ---- value (not `false` or `nil`) to delete the autocommand. Receives one argument, ---- a table with these keys: [event-args]() +--- value (not `false` or `nil`) to delete the autocommand, and receives one argument, a +--- table with these keys: [event-args]() --- - id: (number) autocommand id --- - event: (string) name of the triggered event `autocmd-events` --- - group: (number|nil) autocommand group id, if any @@ -1796,41 +1796,40 @@ function vim.api.nvim_open_term(buffer, opts) end --- region is hidden by setting `eob` flag of --- 'fillchars' to a space char, and clearing the --- `hl-EndOfBuffer` region in 'winhighlight'. ---- - border: Style of (optional) window border. This can either be a string ---- or an array. The string values are the same as those described in 'winborder'. ---- If it is an array, it should have a length of eight or any divisor of ---- eight. The array will specify the eight chars building up the border ---- in a clockwise fashion starting with the top-left corner. As an ---- example, the double box style could be specified as: ---- ``` ---- [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. ---- ``` ---- If the number of chars are less than eight, they will be repeated. Thus ---- an ASCII border could be specified as ---- ``` ---- [ "/", "-", \"\\\\\", "|" ], ---- ``` ---- or all chars the same as ---- ``` ---- [ "x" ]. ---- ``` ---- An empty string can be used to turn off a specific border, for instance, +--- - border: (`string|string[]`) (defaults to 'winborder' option) Window border. The string form +--- accepts the same values as the 'winborder' option. The array form must have a length of +--- eight or any divisor of eight, specifying the chars that form the border in a clockwise +--- fashion starting from the top-left corner. For example, the double-box style can be +--- specified as: --- ``` ---- [ "", "", "", ">", "", "", "", "<" ] +--- [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. --- ``` ---- will only make vertical borders but not horizontal ones. ---- By default, `FloatBorder` highlight is used, which links to `WinSeparator` ---- when not defined. It could also be specified by character: +--- If fewer than eight chars are given, they will be repeated. An ASCII border could be +--- specified as: --- ``` ---- [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +--- [ "/", "-", \"\\\\\", "|" ], --- ``` ---- - title: Title (optional) in window border, string or list. +--- Or one char for all sides: +--- ``` +--- [ "x" ]. +--- ``` +--- Empty string can be used to hide a specific border. This example will show only vertical +--- borders, not horizontal: +--- ``` +--- [ "", "", "", ">", "", "", "", "<" ] +--- ``` +--- By default, `hl-FloatBorder` highlight is used, which links to `hl-WinSeparator` when not +--- defined. Each border side can specify an optional highlight: +--- ``` +--- [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +--- ``` +--- - title: (optional) Title in window border, string or list. --- List should consist of `[text, highlight]` tuples. --- If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. --- - title_pos: Title position. Must be set with `title` option. --- Value can be one of "left", "center", or "right". --- Default is `"left"`. ---- - footer: Footer (optional) in window border, string or list. +--- - footer: (optional) Footer in window border, string or list. --- List should consist of `[text, highlight]` tuples. --- If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. --- - footer_pos: Footer position. Must be set with `footer` option. diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 20d8ac3058..9ff4770e7c 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1716,6 +1716,21 @@ vim.go.dex = vim.go.diffexpr --- Use the indent heuristic for the internal --- diff library. --- +--- inline:{text} Highlight inline differences within a change. +--- See `view-diffs`. Supported values are: +--- +--- none Do not perform inline highlighting. +--- simple Highlight from first different +--- character to the last one in each +--- line. This is the default if no +--- `inline:` value is set. +--- char Use internal diff to perform a +--- character-wise diff and highlight the +--- difference. +--- word Use internal diff to perform a +--- `word`-wise diff and highlight the +--- difference. +--- --- internal Use the internal diff library. This is --- ignored when 'diffexpr' is set. *E960* --- When running out of memory when writing a @@ -1766,7 +1781,7 @@ vim.go.dex = vim.go.diffexpr --- --- --- @type string -vim.o.diffopt = "internal,filler,closeoff,linematch:40" +vim.o.diffopt = "internal,filler,closeoff,inline:simple,linematch:40" vim.o.dip = vim.o.diffopt vim.go.diffopt = vim.o.diffopt vim.go.dip = vim.go.diffopt @@ -4787,6 +4802,17 @@ vim.o.ph = vim.o.pumheight vim.go.pumheight = vim.o.pumheight vim.go.ph = vim.go.pumheight +--- Maximum width for the popup menu (`ins-completion-menu`). When zero, +--- there is no maximum width limit, otherwise the popup menu will never be +--- wider than this value. Truncated text will be indicated by "..." at the +--- end. Takes precedence over 'pumwidth'. +--- +--- @type integer +vim.o.pummaxwidth = 0 +vim.o.pmw = vim.o.pummaxwidth +vim.go.pummaxwidth = vim.o.pummaxwidth +vim.go.pmw = vim.go.pummaxwidth + --- Minimum width for the popup menu (`ins-completion-menu`). If the --- cursor column + 'pumwidth' exceeds screen width, the popup menu is --- nudged to fit on the screen. diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index c790779d51..294f2a357a 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -275,7 +275,7 @@ end --- --- @param msg string function M.ok(msg) - local input = format_report_message('OK', msg) + local input = format_report_message('✅ OK', msg) collect_output(input) end @@ -284,7 +284,7 @@ end --- @param msg string --- @param ... string|string[] Optional advice function M.warn(msg, ...) - local input = format_report_message('WARNING', msg, ...) + local input = format_report_message('⚠️ WARNING', msg, ...) collect_output(input) end @@ -293,7 +293,7 @@ end --- @param msg string --- @param ... string|string[] Optional advice function M.error(msg, ...) - local input = format_report_message('ERROR', msg, ...) + local input = format_report_message('❌ ERROR', msg, ...) collect_output(input) end @@ -449,11 +449,15 @@ function M._check(mods, plugin_names) vim.print('') -- Quit with 'q' inside healthcheck buffers. - vim.keymap.set('n', 'q', function() - if not pcall(vim.cmd.close) then - vim.cmd.bdelete() + vim._with({ buf = bufnr }, function() + if vim.fn.maparg('q', 'n', false, false) == '' then + vim.keymap.set('n', 'q', function() + if not pcall(vim.cmd.close) then + vim.cmd.bdelete() + end + end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) end - end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) + end) -- Once we're done writing checks, set nomodifiable. vim.bo[bufnr].modifiable = false diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index bb4e1cd28f..995340d751 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -296,49 +296,64 @@ end --- root_dir matches. --- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean ---- Update the configuration for an LSP client. +--- Sets the default configuration for an LSP client (or _all_ clients if the special name "*" is +--- used). --- ---- Use name '*' to set default configuration for all clients. ---- ---- Can also be table-assigned to redefine the configuration for a client. +--- Can also be accessed by table-indexing (`vim.lsp.config[…]`) to get the resolved config, or +--- redefine the config (instead of "merging" with the config chain). --- --- Examples: --- ---- - Add a root marker for all clients: +--- - Add root markers for ALL clients: +--- ```lua +--- vim.lsp.config('*', { +--- root_markers = { '.git', '.hg' }, +--- }) +--- ``` +--- - Add capabilities to ALL clients: --- ```lua ---- vim.lsp.config('*', { ---- root_markers = { '.git' }, ---- }) ---- ``` ---- - Add additional capabilities to all clients: +--- vim.lsp.config('*', { +--- capabilities = { +--- textDocument = { +--- semanticTokens = { +--- multilineTokenSupport = true, +--- } +--- } +--- } +--- }) +--- ``` +--- - Add root markers and capabilities for "clangd": --- ```lua ---- vim.lsp.config('*', { ---- capabilities = { ---- textDocument = { ---- semanticTokens = { ---- multilineTokenSupport = true, ---- } ---- } ---- } ---- }) ---- ``` ---- - (Re-)define the configuration for clangd: +--- vim.lsp.config('clangd', { +--- root_markers = { '.clang-format', 'compile_commands.json' }, +--- capabilities = { +--- textDocument = { +--- completion = { +--- completionItem = { +--- snippetSupport = true, +--- } +--- } +--- } +--- } +--- }) +--- ``` +--- - (Re-)define the "clangd" configuration (overrides the resolved chain): --- ```lua ---- vim.lsp.config.clangd = { ---- cmd = { ---- 'clangd', ---- '--clang-tidy', ---- '--background-index', ---- '--offset-encoding=utf-8', ---- }, ---- root_markers = { '.clangd', 'compile_commands.json' }, ---- filetypes = { 'c', 'cpp' }, ---- } ---- ``` ---- - Get configuration for luals: +--- vim.lsp.config.clangd = { +--- cmd = { +--- 'clangd', +--- '--clang-tidy', +--- '--background-index', +--- '--offset-encoding=utf-8', +--- }, +--- root_markers = { '.clangd', 'compile_commands.json' }, +--- filetypes = { 'c', 'cpp' }, +--- } +--- ``` +--- - Get the resolved configuration for "luals": --- ```lua ---- local cfg = vim.lsp.config.luals ---- ``` +--- local cfg = vim.lsp.config.luals +--- ``` --- --- @param name string --- @param cfg vim.lsp.Config @@ -615,6 +630,17 @@ function lsp.start(config, opts) config.root_dir = vim.fs.root(bufnr, opts._root_markers) end + if + not config.root_dir + and (not config.workspace_folders or #config.workspace_folders == 0) + and config.workspace_required + then + log.info( + ('skipping config "%s": workspace_required=true, no workspace found'):format(config.name) + ) + return + end + for _, client in pairs(all_clients) do if reuse_client(client, config) then if opts.attach == false then @@ -1230,7 +1256,9 @@ end --- ---@param bufnr integer Buffer handle, or 0 for current. ---@param method string LSP method name ----@param params table? Parameters to send to the server +---@param params? table|(fun(client: vim.lsp.Client, bufnr: integer): table?) Parameters to send to the server. +--- Can also be passed as a function that returns the params table for cases where +--- parameters are specific to the client. ---@param timeout_ms integer? Maximum time in milliseconds to wait for a result. --- (default: `1000`) ---@return table<integer, {error: lsp.ResponseError?, result: any}>? result Map of client_id:request_result. @@ -1511,25 +1539,39 @@ function lsp.with(handler, override_config) end end ---- Registry for client side commands. ---- This is an extension point for plugins to handle custom commands which are ---- not part of the core language server protocol specification. +--- Registry (a table) for client-side handlers, for custom server-commands that are not in the LSP +--- specification. --- ---- The registry is a table where the key is a unique command name, ---- and the value is a function which is called if any LSP action ---- (code action, code lenses, ...) triggers the command. +--- If an LSP response contains a command which is not found in `vim.lsp.commands`, the command will +--- be executed via the LSP server using `workspace/executeCommand`. --- ---- If an LSP response contains a command for which no matching entry is ---- available in this registry, the command will be executed via the LSP server ---- using `workspace/executeCommand`. +--- Each key in the table is a unique command name, and each value is a function which is called +--- when an LSP action (code action, code lenses, …) triggers the command. --- ---- The first argument to the function will be the `Command`: +--- - Argument 1 is the `Command`: +--- ``` --- Command --- title: String --- command: String --- arguments?: any[] +--- ``` +--- - Argument 2 is the |lsp-handler| `ctx`. +--- +--- Example: +--- +--- ```lua +--- vim.lsp.commands['java.action.generateToStringPrompt'] = function(_, ctx) +--- require("jdtls.async").run(function() +--- local _, result = request(ctx.bufnr, 'java/checkToStringStatus', ctx.params) +--- local fields = ui.pick_many(result.fields, 'Include item in toString?', function(x) +--- return string.format('%s: %s', x.name, x.type) +--- end) +--- local _, edit = request(ctx.bufnr, 'java/generateToString', { context = ctx.params; fields = fields; }) +--- vim.lsp.util.apply_workspace_edit(edit, offset_encoding) +--- end) +--- end +--- ``` --- ---- The second argument is the `ctx` of |lsp-handler| --- @type table<string,function> lsp.commands = setmetatable({}, { __newindex = function(tbl, key, value) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 8c75ee321d..b256eab1a6 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -63,6 +63,9 @@ local validate = vim.validate --- folder in this list. See `workspaceFolders` in the LSP spec. --- @field workspace_folders? lsp.WorkspaceFolder[] --- +--- (default false) Server requires a workspace (no "single file" support). +--- @field workspace_required? boolean +--- --- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|, --- passed to the language server on initialization. Hint: use make_client_capabilities() and modify --- its result. diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 8af9f2f791..04e8393eb3 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -187,26 +187,32 @@ local function check_enabled_configs() local config = vim.lsp.config[name] local text = {} --- @type string[] text[#text + 1] = ('%s:'):format(name) - for k, v in - vim.spairs(config --[[@as table<string,any>]]) - do - local v_str --- @type string? - if k == 'name' then - v_str = nil - elseif k == 'filetypes' or k == 'root_markers' then - v_str = table.concat(v, ', ') - elseif type(v) == 'function' then - v_str = func_tostring(v) - else - v_str = vim.inspect(v, { newline = '\n ' }) - end + if not config then + report_warn( + ("'%s' config not found. Ensure that vim.lsp.config('%s') was called."):format(name, name) + ) + else + for k, v in + vim.spairs(config --[[@as table<string,any>]]) + do + local v_str --- @type string? + if k == 'name' then + v_str = nil + elseif k == 'filetypes' or k == 'root_markers' then + v_str = table.concat(v, ', ') + elseif type(v) == 'function' then + v_str = func_tostring(v) + else + v_str = vim.inspect(v, { newline = '\n ' }) + end - if k == 'cmd' and type(v) == 'table' and vim.fn.executable(v[1]) == 0 then - report_warn(("'%s' is not executable. Configuration will not be used."):format(v[1])) - end + if k == 'cmd' and type(v) == 'table' and vim.fn.executable(v[1]) == 0 then + report_warn(("'%s' is not executable. Configuration will not be used."):format(v[1])) + end - if v_str then - text[#text + 1] = ('- %s: %s'):format(k, v_str) + if v_str then + text[#text + 1] = ('- %s: %s'):format(k, v_str) + end end end text[#text + 1] = '' diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index ab3a269937..a37fa42aac 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -94,10 +94,10 @@ function M.on_refresh(err, _, ctx) for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do for _, winid in ipairs(api.nvim_list_wins()) do if api.nvim_win_get_buf(winid) == bufnr then - if bufstates[bufnr] then + if bufstates[bufnr] and bufstates[bufnr].enabled then bufstates[bufnr].applied = {} + util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) end - util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) end end end diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index fa01951b02..e4b5206fa4 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -107,9 +107,22 @@ local function provider_disabled(provider) return false end +--- Checks the hygiene of a `g:loaded_xx_provider` variable. +local function check_loaded_var(var) + if vim.g[var] == 1 then + health.error(('`g:%s=1` may have been set by mistake.'):format(var), { + ('Remove `vim.g.%s=1` from your config.'):format(var), + 'To disable the provider, set this to 0, not 1.', + 'If you want to enable the provider but skip automatic detection, set the respective `g:…_host_prog` var. See :help provider', + }) + end +end + local function clipboard() health.start('Clipboard (optional)') + check_loaded_var('loaded_clipboard_provider') + if os.getenv('TMUX') and vim.fn.executable('tmux') == 1 @@ -144,6 +157,8 @@ end local function node() health.start('Node.js provider (optional)') + check_loaded_var('loaded_node_provider') + if provider_disabled('node') then return end @@ -247,6 +262,8 @@ end local function perl() health.start('Perl provider (optional)') + check_loaded_var('loaded_perl_provider') + if provider_disabled('perl') then return end @@ -256,7 +273,7 @@ local function perl() if not perl_exec then health.warn(assert(perl_warnings), { 'See :help provider-perl for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', }) health.warn('No usable perl executable found') return @@ -547,7 +564,7 @@ local function version_info(python) local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]]) local version_status = 'unknown; ' .. nvim_path_base - if is_bad_response(nvim_version) and is_bad_response(pypi_version) then + if not is_bad_response(nvim_version) and not is_bad_response(pypi_version) then if vim.version.lt(nvim_version, pypi_version) then version_status = 'outdated; from ' .. nvim_path_base else @@ -561,6 +578,8 @@ end local function python() health.start('Python 3 provider (optional)') + check_loaded_var('loaded_python3_provider') + local python_exe = '' local virtual_env = os.getenv('VIRTUAL_ENV') local venv = virtual_env and vim.fn.resolve(virtual_env) or '' @@ -595,7 +614,7 @@ local function python() if pythonx_warnings then health.warn(pythonx_warnings, { 'See :help provider-python for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', }) elseif pyname and pyname ~= '' and python_exe == '' then if not vim.g[host_prog_var] then @@ -840,6 +859,8 @@ end local function ruby() health.start('Ruby provider (optional)') + check_loaded_var('loaded_ruby_provider') + if provider_disabled('ruby') then return end @@ -860,7 +881,7 @@ local function ruby() 'Run `gem environment` to ensure the gem bin directory is in $PATH.', 'If you are using rvm/rbenv/chruby, try "rehashing".', 'See :help g:ruby_host_prog for non-standard gem installations.', - 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', }) return end diff --git a/runtime/lua/vim/provider/python.lua b/runtime/lua/vim/provider/python.lua index 48f08302f9..a772b36973 100644 --- a/runtime/lua/vim/provider/python.lua +++ b/runtime/lua/vim/provider/python.lua @@ -1,5 +1,5 @@ local M = {} -local min_version = '3.7' +local min_version = '3.9' local s_err ---@type string? local s_host ---@type string? diff --git a/runtime/lua/vim/treesitter/_headings.lua b/runtime/lua/vim/treesitter/_headings.lua index 885d014a89..bfa468ad88 100644 --- a/runtime/lua/vim/treesitter/_headings.lua +++ b/runtime/lua/vim/treesitter/_headings.lua @@ -40,10 +40,6 @@ local heading_queries = { ]], } -local function hash_tick(bufnr) - return tostring(vim.b[bufnr].changedtick) -end - ---@class TS.Heading ---@field bufnr integer ---@field lnum integer @@ -53,7 +49,7 @@ end --- Extract headings from buffer --- @param bufnr integer buffer to extract headings from --- @return TS.Heading[] -local get_headings = vim.func._memoize(hash_tick, function(bufnr) +local get_headings = function(bufnr) local lang = ts.language.get_lang(vim.bo[bufnr].filetype) if not lang then return {} @@ -85,7 +81,7 @@ local get_headings = vim.func._memoize(hash_tick, function(bufnr) end end return headings -end) +end --- Shows an Outline (table of contents) of the current buffer, in the loclist. function M.show_toc() diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua index 07a1c921c7..9b9cc4eb54 100644 --- a/runtime/lua/vim/treesitter/_meta/misc.lua +++ b/runtime/lua/vim/treesitter/_meta/misc.lua @@ -5,12 +5,10 @@ error('Cannot require a meta file') ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) ---@class TSParser: userdata ----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean, timeout_ns: integer?): TSTree?, (Range4|Range6)[] ---@field reset fun(self: TSParser) ---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) ----@field set_timeout fun(self: TSParser, timeout: integer) ----@field timeout fun(self: TSParser): integer ---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) ---@field _logger fun(self: TSParser): TSLoggerCallback diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index f2e745ec65..6f0e377d2f 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -43,8 +43,10 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') +local hrtime = vim.uv.hrtime -local default_parse_timeout_ms = 3 +-- Parse in 3ms chunks. +local default_parse_timeout_ns = 3 * 1000000 ---@type Range2 local entire_document_range = { 0, math.huge } @@ -198,16 +200,16 @@ function LanguageTree:_set_logger() self._parser:_set_logger(log_lex, log_parse, self._logger) end ----Measure execution time of a function +---Measure execution time of a function, in nanoseconds. ---@generic R1, R2, R3 ---@param f fun(): R1, R2, R3 ---@return number, R1, R2, R3 local function tcall(f, ...) - local start = vim.uv.hrtime() + local start = hrtime() ---@diagnostic disable-next-line local r = { f(...) } --- @type number - local duration = (vim.uv.hrtime() - start) / 1000000 + local duration = hrtime() - start --- @diagnostic disable-next-line: redundant-return-value return duration, unpack(r) end @@ -388,18 +390,29 @@ function LanguageTree:_parse_regions(range, thread_state) ) then self._parser:set_included_ranges(ranges) - self._parser:set_timeout(thread_state.timeout and thread_state.timeout * 1000 or 0) -- ms -> micros - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + local parse_time, tree, tree_changes = tcall( + self._parser.parse, + self._parser, + self._trees[i], + self._source, + true, + thread_state.timeout + ) while true do if tree then break end coroutine.yield(self._trees, false) - parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + parse_time, tree, tree_changes = tcall( + self._parser.parse, + self._parser, + self._trees[i], + self._source, + true, + thread_state.timeout + ) end self:_subtract_time(thread_state, parse_time) @@ -503,7 +516,7 @@ function LanguageTree:_async_parse(range, on_parse) local buf = is_buffer_parser and vim.b[source] or nil local ct = is_buffer_parser and buf.changedtick or nil local total_parse_time = 0 - local redrawtime = vim.o.redrawtime + local redrawtime = vim.o.redrawtime * 1000000 local thread_state = {} ---@type ParserThreadState @@ -526,7 +539,7 @@ function LanguageTree:_async_parse(range, on_parse) end end - thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ns or nil local parse_time, trees, finished = tcall(parse, self, range, thread_state) total_parse_time = total_parse_time + parse_time @@ -868,35 +881,42 @@ end ---@alias vim.treesitter.languagetree.Injection table<string,table<integer,vim.treesitter.languagetree.InjectionElem>> ----@param t table<integer,vim.treesitter.languagetree.Injection> ----@param tree_index integer +---@param t vim.treesitter.languagetree.Injection ---@param pattern integer ---@param lang string ---@param combined boolean ---@param ranges Range6[] -local function add_injection(t, tree_index, pattern, lang, combined, ranges) +---@param result table<string,Range6[][]> +local function add_injection(t, pattern, lang, combined, ranges, result) if #ranges == 0 then -- Make sure not to add an empty range set as this is interpreted to mean the whole buffer. return end - -- Each tree index should be isolated from the other nodes. - if not t[tree_index] then - t[tree_index] = {} + if not result[lang] then + result[lang] = {} end - if not t[tree_index][lang] then - t[tree_index][lang] = {} + if not combined then + table.insert(result[lang], ranges) + return end - -- Key this by pattern. If combined is set to true all captures of this pattern + if not t[lang] then + t[lang] = {} + end + + -- Key this by pattern. For combined injections, all captures of this pattern -- will be parsed by treesitter as the same "source". - -- If combined is false, each "region" will be parsed as a single source. - if not t[tree_index][lang][pattern] then - t[tree_index][lang][pattern] = { combined = combined, regions = {} } + if not t[lang][pattern] then + local regions = {} + t[lang][pattern] = regions + table.insert(result[lang], regions) end - table.insert(t[tree_index][lang][pattern].regions, ranges) + for _, range in ipairs(ranges) do + table.insert(t[lang][pattern], range) + end end -- TODO(clason): replace by refactored `ts.has_parser` API (without side effects) @@ -964,19 +984,6 @@ function LanguageTree:_get_injection(match, metadata) return lang, combined, ranges end ---- Can't use vim.tbl_flatten since a range is just a table. ----@param regions Range6[][] ----@return Range6[] -local function combine_regions(regions) - local result = {} ---@type Range6[] - for _, region in ipairs(regions) do - for _, range in ipairs(region) do - result[#result + 1] = range - end - end - return result -end - --- Gets language injection regions by language. --- --- This is where most of the injection processing occurs. @@ -993,13 +1000,16 @@ function LanguageTree:_get_injections(range, thread_state) return {} end - ---@type table<integer,vim.treesitter.languagetree.Injection> - local injections = {} - local start = vim.uv.hrtime() + local start = hrtime() + + ---@type table<string,Range6[][]> + local result = {} local full_scan = range == true or self._injection_query.has_combined_injections - for index, tree in pairs(self._trees) do + for _, tree in pairs(self._trees) do + ---@type vim.treesitter.languagetree.Injection + local injections = {} local root_node = tree:root() local start_line, end_line ---@type integer, integer if full_scan then @@ -1013,38 +1023,15 @@ function LanguageTree:_get_injections(range, thread_state) do local lang, combined, ranges = self:_get_injection(match, metadata) if lang then - add_injection(injections, index, pattern, lang, combined, ranges) + add_injection(injections, pattern, lang, combined, ranges, result) else self:_log('match from injection query failed for pattern', pattern) end -- Check the current function duration against the timeout, if it exists. - local current_time = vim.uv.hrtime() - self:_subtract_time(thread_state, (current_time - start) / 1000000) - start = current_time - end - end - - ---@type table<string,Range6[][]> - local result = {} - - -- Generate a map by lang of node lists. - -- Each list is a set of ranges that should be parsed together. - for _, lang_map in pairs(injections) do - for lang, patterns in pairs(lang_map) do - if not result[lang] then - result[lang] = {} - end - - for _, entry in pairs(patterns) do - if entry.combined then - table.insert(result[lang], combine_regions(entry.regions)) - else - for _, ranges in pairs(entry.regions) do - table.insert(result[lang], ranges) - end - end - end + local current_time = hrtime() + self:_subtract_time(thread_state, current_time - start) + start = hrtime() end end |