aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_defaults.lua4
-rw-r--r--runtime/lua/vim/_meta/api.lua55
-rw-r--r--runtime/lua/vim/_meta/options.lua28
-rw-r--r--runtime/lua/vim/health.lua18
-rw-r--r--runtime/lua/vim/lsp.lua136
-rw-r--r--runtime/lua/vim/lsp/client.lua3
-rw-r--r--runtime/lua/vim/lsp/health.lua42
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua4
-rw-r--r--runtime/lua/vim/provider/health.lua29
-rw-r--r--runtime/lua/vim/provider/python.lua2
-rw-r--r--runtime/lua/vim/treesitter/_headings.lua8
-rw-r--r--runtime/lua/vim/treesitter/_meta/misc.lua4
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua123
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