aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-11-25 19:15:05 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-11-25 19:27:38 +0000
commitc5d770d311841ea5230426cc4c868e8db27300a8 (patch)
treedd21f70127b4b8b5f109baefc8ecc5016f507c91 /runtime/lua/vim
parent9be89f131f87608f224f0ee06d199fcd09d32176 (diff)
parent081beb3659bd6d8efc3e977a160b1e72becbd8a2 (diff)
downloadrneovim-c5d770d311841ea5230426cc4c868e8db27300a8.tar.gz
rneovim-c5d770d311841ea5230426cc4c868e8db27300a8.tar.bz2
rneovim-c5d770d311841ea5230426cc4c868e8db27300a8.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_buf.lua23
-rw-r--r--runtime/lua/vim/_defaults.lua160
-rw-r--r--runtime/lua/vim/_editor.lua285
-rw-r--r--runtime/lua/vim/_meta.lua2
-rw-r--r--runtime/lua/vim/_meta/api.lua39
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua1
-rw-r--r--runtime/lua/vim/_meta/builtin.lua31
-rw-r--r--runtime/lua/vim/_meta/builtin_types.lua91
-rw-r--r--runtime/lua/vim/_meta/options.lua348
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua43
-rw-r--r--runtime/lua/vim/_meta/vvars.lua3
-rw-r--r--runtime/lua/vim/_options.lua4
-rw-r--r--runtime/lua/vim/_system.lua17
-rw-r--r--runtime/lua/vim/_watch.lua16
-rw-r--r--runtime/lua/vim/diagnostic.lua289
-rw-r--r--runtime/lua/vim/filetype.lua117
-rw-r--r--runtime/lua/vim/filetype/detect.lua13
-rw-r--r--runtime/lua/vim/fs.lua34
-rw-r--r--runtime/lua/vim/func/_memoize.lua6
-rw-r--r--runtime/lua/vim/glob.lua2
-rw-r--r--runtime/lua/vim/hl.lua (renamed from runtime/lua/vim/highlight.lua)26
-rw-r--r--runtime/lua/vim/keymap.lua18
-rw-r--r--runtime/lua/vim/loader.lua443
-rw-r--r--runtime/lua/vim/lsp.lua95
-rw-r--r--runtime/lua/vim/lsp/_dynamic.lua110
-rw-r--r--runtime/lua/vim/lsp/_meta.lua3
-rw-r--r--runtime/lua/vim/lsp/_tagfunc.lua49
-rw-r--r--runtime/lua/vim/lsp/_watchfiles.lua10
-rw-r--r--runtime/lua/vim/lsp/buf.lua702
-rw-r--r--runtime/lua/vim/lsp/client.lua255
-rw-r--r--runtime/lua/vim/lsp/codelens.lua4
-rw-r--r--runtime/lua/vim/lsp/completion.lua70
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua109
-rw-r--r--runtime/lua/vim/lsp/handlers.lua308
-rw-r--r--runtime/lua/vim/lsp/health.lua23
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua33
-rw-r--r--runtime/lua/vim/lsp/log.lua9
-rw-r--r--runtime/lua/vim/lsp/protocol.lua112
-rw-r--r--runtime/lua/vim/lsp/rpc.lua103
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua110
-rw-r--r--runtime/lua/vim/lsp/sync.lua59
-rw-r--r--runtime/lua/vim/lsp/util.lua1101
-rw-r--r--runtime/lua/vim/provider/health.lua2
-rw-r--r--runtime/lua/vim/secure.lua20
-rw-r--r--runtime/lua/vim/shared.lua354
-rw-r--r--runtime/lua/vim/termcap.lua8
-rw-r--r--runtime/lua/vim/treesitter.lua19
-rw-r--r--runtime/lua/vim/treesitter/_meta/tsnode.lua18
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua8
-rw-r--r--runtime/lua/vim/treesitter/dev.lua19
-rw-r--r--runtime/lua/vim/treesitter/health.lua18
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua14
-rw-r--r--runtime/lua/vim/treesitter/language.lua21
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua2
-rw-r--r--runtime/lua/vim/treesitter/query.lua2
-rw-r--r--runtime/lua/vim/ui.lua33
56 files changed, 3050 insertions, 2764 deletions
diff --git a/runtime/lua/vim/_buf.lua b/runtime/lua/vim/_buf.lua
new file mode 100644
index 0000000000..0631c96f77
--- /dev/null
+++ b/runtime/lua/vim/_buf.lua
@@ -0,0 +1,23 @@
+local M = {}
+
+--- Adds one or more blank lines above or below the cursor.
+-- TODO: move to _defaults.lua once it is possible to assign a Lua function to options #25672
+--- @param above? boolean Place blank line(s) above the cursor
+local function add_blank(above)
+ local offset = above and 1 or 0
+ local repeated = vim.fn['repeat']({ '' }, vim.v.count1)
+ local linenr = vim.api.nvim_win_get_cursor(0)[1]
+ vim.api.nvim_buf_set_lines(0, linenr - offset, linenr - offset, true, repeated)
+end
+
+-- TODO: move to _defaults.lua once it is possible to assign a Lua function to options #25672
+function M.space_above()
+ add_blank(true)
+end
+
+-- TODO: move to _defaults.lua once it is possible to assign a Lua function to options #25672
+function M.space_below()
+ add_blank()
+end
+
+return M
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 6cad1dbca9..06f6ed6829 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -157,7 +157,7 @@ do
--- client is attached. If no client is attached, or if a server does not support a capability, an
--- error message is displayed rather than exhibiting different behavior.
---
- --- See |grr|, |grn|, |gra|, |i_CTRL-S|.
+ --- See |grr|, |grn|, |gra|, |gri|, |gO|, |i_CTRL-S|.
do
vim.keymap.set('n', 'grn', function()
vim.lsp.buf.rename()
@@ -171,7 +171,15 @@ do
vim.lsp.buf.references()
end, { desc = 'vim.lsp.buf.references()' })
- vim.keymap.set('i', '<C-S>', function()
+ vim.keymap.set('n', 'gri', function()
+ vim.lsp.buf.implementation()
+ end, { desc = 'vim.lsp.buf.implementation()' })
+
+ vim.keymap.set('n', 'gO', function()
+ vim.lsp.buf.document_symbol()
+ end, { desc = 'vim.lsp.buf.document_symbol()' })
+
+ vim.keymap.set({ 'i', 's' }, '<C-S>', function()
vim.lsp.buf.signature_help()
end, { desc = 'vim.lsp.buf.signature_help()' })
end
@@ -211,173 +219,163 @@ do
--- vim-unimpaired style mappings. See: https://github.com/tpope/vim-unimpaired
do
+ --- Execute a command and print errors without a stacktrace.
+ --- @param opts table Arguments to |nvim_cmd()|
+ local function cmd(opts)
+ local _, err = pcall(vim.api.nvim_cmd, opts, {})
+ if err then
+ vim.api.nvim_err_writeln(err:sub(#'Vim:' + 1))
+ end
+ end
+
-- Quickfix mappings
vim.keymap.set('n', '[q', function()
- vim.cmd.cprevious({ count = vim.v.count1 })
- end, {
- desc = ':cprevious',
- })
+ cmd({ cmd = 'cprevious', count = vim.v.count1 })
+ end, { desc = ':cprevious' })
vim.keymap.set('n', ']q', function()
- vim.cmd.cnext({ count = vim.v.count1 })
- end, {
- desc = ':cnext',
- })
+ cmd({ cmd = 'cnext', count = vim.v.count1 })
+ end, { desc = ':cnext' })
vim.keymap.set('n', '[Q', function()
- vim.cmd.crewind({ count = vim.v.count ~= 0 and vim.v.count or nil })
- end, {
- desc = ':crewind',
- })
+ cmd({ cmd = 'crewind', count = vim.v.count ~= 0 and vim.v.count or nil })
+ end, { desc = ':crewind' })
vim.keymap.set('n', ']Q', function()
- vim.cmd.clast({ count = vim.v.count ~= 0 and vim.v.count or nil })
- end, {
- desc = ':clast',
- })
+ cmd({ cmd = 'clast', count = vim.v.count ~= 0 and vim.v.count or nil })
+ end, { desc = ':clast' })
vim.keymap.set('n', '[<C-Q>', function()
- vim.cmd.cpfile({ count = vim.v.count1 })
- end, {
- desc = ':cpfile',
- })
+ cmd({ cmd = 'cpfile', count = vim.v.count1 })
+ end, { desc = ':cpfile' })
vim.keymap.set('n', ']<C-Q>', function()
- vim.cmd.cnfile({ count = vim.v.count1 })
- end, {
- desc = ':cnfile',
- })
+ cmd({ cmd = 'cnfile', count = vim.v.count1 })
+ end, { desc = ':cnfile' })
-- Location list mappings
vim.keymap.set('n', '[l', function()
- vim.cmd.lprevious({ count = vim.v.count1 })
- end, {
- desc = ':lprevious',
- })
+ cmd({ cmd = 'lprevious', count = vim.v.count1 })
+ end, { desc = ':lprevious' })
vim.keymap.set('n', ']l', function()
- vim.cmd.lnext({ count = vim.v.count1 })
- end, {
- desc = ':lnext',
- })
+ cmd({ cmd = 'lnext', count = vim.v.count1 })
+ end, { desc = ':lnext' })
vim.keymap.set('n', '[L', function()
- vim.cmd.lrewind({ count = vim.v.count ~= 0 and vim.v.count or nil })
- end, {
- desc = ':lrewind',
- })
+ cmd({ cmd = 'lrewind', count = vim.v.count ~= 0 and vim.v.count or nil })
+ end, { desc = ':lrewind' })
vim.keymap.set('n', ']L', function()
- vim.cmd.llast({ count = vim.v.count ~= 0 and vim.v.count or nil })
- end, {
- desc = ':llast',
- })
+ cmd({ cmd = 'llast', count = vim.v.count ~= 0 and vim.v.count or nil })
+ end, { desc = ':llast' })
vim.keymap.set('n', '[<C-L>', function()
- vim.cmd.lpfile({ count = vim.v.count1 })
- end, {
- desc = ':lpfile',
- })
+ cmd({ cmd = 'lpfile', count = vim.v.count1 })
+ end, { desc = ':lpfile' })
vim.keymap.set('n', ']<C-L>', function()
- vim.cmd.lnfile({ count = vim.v.count1 })
- end, {
- desc = ':lnfile',
- })
+ cmd({ cmd = 'lnfile', count = vim.v.count1 })
+ end, { desc = ':lnfile' })
-- Argument list
vim.keymap.set('n', '[a', function()
- vim.cmd.previous({ count = vim.v.count1 })
- end, {
- desc = ':previous',
- })
+ cmd({ cmd = 'previous', count = vim.v.count1 })
+ end, { desc = ':previous' })
vim.keymap.set('n', ']a', function()
-- count doesn't work with :next, must use range. See #30641.
- vim.cmd.next({ range = { vim.v.count1 } })
- end, {
- desc = ':next',
- })
+ cmd({ cmd = 'next', range = { vim.v.count1 } })
+ end, { desc = ':next' })
vim.keymap.set('n', '[A', function()
if vim.v.count ~= 0 then
- vim.cmd.argument({ count = vim.v.count })
+ cmd({ cmd = 'argument', count = vim.v.count })
else
- vim.cmd.rewind()
+ cmd({ cmd = 'rewind' })
end
- end, {
- desc = ':rewind',
- })
+ end, { desc = ':rewind' })
vim.keymap.set('n', ']A', function()
if vim.v.count ~= 0 then
- vim.cmd.argument({ count = vim.v.count })
+ cmd({ cmd = 'argument', count = vim.v.count })
else
- vim.cmd.last()
+ cmd({ cmd = 'last' })
end
- end, {
- desc = ':last',
- })
+ end, { desc = ':last' })
-- Tags
vim.keymap.set('n', '[t', function()
-- count doesn't work with :tprevious, must use range. See #30641.
- vim.cmd.tprevious({ range = { vim.v.count1 } })
+ cmd({ cmd = 'tprevious', range = { vim.v.count1 } })
end, { desc = ':tprevious' })
vim.keymap.set('n', ']t', function()
-- count doesn't work with :tnext, must use range. See #30641.
- vim.cmd.tnext({ range = { vim.v.count1 } })
+ cmd({ cmd = 'tnext', range = { vim.v.count1 } })
end, { desc = ':tnext' })
vim.keymap.set('n', '[T', function()
-- count doesn't work with :trewind, must use range. See #30641.
- vim.cmd.trewind({ range = vim.v.count ~= 0 and { vim.v.count } or nil })
+ cmd({ cmd = 'trewind', range = vim.v.count ~= 0 and { vim.v.count } or nil })
end, { desc = ':trewind' })
vim.keymap.set('n', ']T', function()
-- :tlast does not accept a count, so use :trewind if count given
if vim.v.count ~= 0 then
- vim.cmd.trewind({ range = { vim.v.count } })
+ cmd({ cmd = 'trewind', range = { vim.v.count } })
else
- vim.cmd.tlast()
+ cmd({ cmd = 'tlast' })
end
end, { desc = ':tlast' })
vim.keymap.set('n', '[<C-T>', function()
-- count doesn't work with :ptprevious, must use range. See #30641.
- vim.cmd.ptprevious({ range = { vim.v.count1 } })
+ cmd({ cmd = 'ptprevious', range = { vim.v.count1 } })
end, { desc = ' :ptprevious' })
vim.keymap.set('n', ']<C-T>', function()
-- count doesn't work with :ptnext, must use range. See #30641.
- vim.cmd.ptnext({ range = { vim.v.count1 } })
+ cmd({ cmd = 'ptnext', range = { vim.v.count1 } })
end, { desc = ':ptnext' })
-- Buffers
vim.keymap.set('n', '[b', function()
- vim.cmd.bprevious({ count = vim.v.count1 })
+ cmd({ cmd = 'bprevious', count = vim.v.count1 })
end, { desc = ':bprevious' })
vim.keymap.set('n', ']b', function()
- vim.cmd.bnext({ count = vim.v.count1 })
+ cmd({ cmd = 'bnext', count = vim.v.count1 })
end, { desc = ':bnext' })
vim.keymap.set('n', '[B', function()
if vim.v.count ~= 0 then
- vim.cmd.buffer({ count = vim.v.count })
+ cmd({ cmd = 'buffer', count = vim.v.count })
else
- vim.cmd.brewind()
+ cmd({ cmd = 'brewind' })
end
end, { desc = ':brewind' })
vim.keymap.set('n', ']B', function()
if vim.v.count ~= 0 then
- vim.cmd.buffer({ count = vim.v.count })
+ cmd({ cmd = 'buffer', count = vim.v.count })
else
- vim.cmd.blast()
+ cmd({ cmd = 'blast' })
end
end, { desc = ':blast' })
+
+ -- Add empty lines
+ vim.keymap.set('n', '[<Space>', function()
+ -- TODO: update once it is possible to assign a Lua function to options #25672
+ vim.go.operatorfunc = "v:lua.require'vim._buf'.space_above"
+ return 'g@l'
+ end, { expr = true, desc = 'Add empty line above cursor' })
+
+ vim.keymap.set('n', ']<Space>', function()
+ -- TODO: update once it is possible to assign a Lua function to options #25672
+ vim.go.operatorfunc = "v:lua.require'vim._buf'.space_below"
+ return 'g@l'
+ end, { expr = true, desc = 'Add empty line below cursor' })
end
end
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 2e829578a7..44f17b3f85 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -32,7 +32,7 @@ for k, v in pairs({
func = true,
F = true,
lsp = true,
- highlight = true,
+ hl = true,
diagnostic = true,
keymap = true,
ui = true,
@@ -68,6 +68,12 @@ vim.log = {
},
}
+local utfs = {
+ ['utf-8'] = true,
+ ['utf-16'] = true,
+ ['utf-32'] = true,
+}
+
-- TODO(lewis6991): document that the signature is system({cmd}, [{opts},] {on_exit})
--- Runs a system command or throws an error if {cmd} cannot be run.
---
@@ -467,15 +473,11 @@ vim.cmd = setmetatable({}, {
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
- local validate = vim.validate
-
--- @param scope string
--- @param handle? false|integer
--- @return vim.var_accessor
local function make_dict_accessor(scope, handle)
- validate({
- scope = { scope, 's' },
- })
+ vim.validate('scope', scope, 'string')
local mt = {}
function mt:__newindex(k, v)
return vim._setvar(scope, handle or 0, k, v)
@@ -543,7 +545,7 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
-- TODO: handle double-width characters
if regtype:byte() == 22 then
local bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
- pos1[2] = vim.str_utfindex(bufline, pos1[2])
+ pos1[2] = vim.str_utfindex(bufline, 'utf-32', pos1[2])
end
local region = {}
@@ -555,14 +557,14 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
c2 = c1 + tonumber(regtype:sub(2))
-- and adjust for non-ASCII characters
local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
- local utflen = vim.str_utfindex(bufline, #bufline)
+ local utflen = vim.str_utfindex(bufline, 'utf-32', #bufline)
if c1 <= utflen then
- c1 = assert(tonumber(vim.str_byteindex(bufline, c1)))
+ c1 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c1)))
else
c1 = #bufline + 1
end
if c2 <= utflen then
- c2 = assert(tonumber(vim.str_byteindex(bufline, c2)))
+ c2 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c2)))
else
c2 = #bufline + 1
end
@@ -591,7 +593,7 @@ end
---@param timeout integer Number of milliseconds to wait before calling `fn`
---@return table timer luv timer object
function vim.defer_fn(fn, timeout)
- vim.validate({ fn = { fn, 'c', true } })
+ vim.validate('fn', fn, 'callable', true)
local timer = assert(vim.uv.new_timer())
timer:start(
timeout,
@@ -649,7 +651,7 @@ do
end
end
-local on_key_cbs = {} --- @type table<integer,function>
+local on_key_cbs = {} --- @type table<integer,[function, table]>
--- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
--- yes every, input key.
@@ -658,64 +660,225 @@ local on_key_cbs = {} --- @type table<integer,function>
--- and cannot be toggled dynamically.
---
---@note {fn} will be removed on error.
+---@note {fn} won't be invoked recursively, i.e. if {fn} itself consumes input,
+--- it won't be invoked for those keys.
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
---
----@param fn fun(key: string, typed: string)?
---- Function invoked on every key press. |i_CTRL-V|
---- {key} is the key after mappings have been applied, and
---- {typed} is the key(s) before mappings are applied, which
---- may be empty if {key} is produced by non-typed keys.
---- When {fn} is nil and {ns_id} is specified, the callback
---- associated with namespace {ns_id} is removed.
+---@param fn nil|fun(key: string, typed: string): string? Function invoked for every input key,
+--- after mappings have been applied but before further processing. Arguments
+--- {key} and {typed} are raw keycodes, where {key} is the key after mappings
+--- are applied, and {typed} is the key(s) before mappings are applied.
+--- {typed} may be empty if {key} is produced by non-typed key(s) or by the
+--- same typed key(s) that produced a previous {key}.
+--- If {fn} returns an empty string, {key} is discarded/ignored.
+--- When {fn} is `nil`, the callback associated with namespace {ns_id} is removed.
---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a
---- new |nvim_create_namespace()| id.
+--- new |nvim_create_namespace()| id.
+---@param opts table? Optional parameters
+---
+---@see |keytrans()|
---
---@return integer Namespace id associated with {fn}. Or count of all callbacks
---if on_key() is called without arguments.
-function vim.on_key(fn, ns_id)
+function vim.on_key(fn, ns_id, opts)
if fn == nil and ns_id == nil then
return vim.tbl_count(on_key_cbs)
end
- vim.validate({
- fn = { fn, 'c', true },
- ns_id = { ns_id, 'n', true },
- })
+ vim.validate('fn', fn, 'callable', true)
+ vim.validate('ns_id', ns_id, 'number', true)
+ vim.validate('opts', opts, 'table', true)
+ opts = opts or {}
if ns_id == nil or ns_id == 0 then
ns_id = vim.api.nvim_create_namespace('')
end
- on_key_cbs[ns_id] = fn
+ on_key_cbs[ns_id] = fn and { fn, opts }
return ns_id
end
--- Executes the on_key callbacks.
---@private
function vim._on_key(buf, typed_buf)
- local failed_ns_ids = {}
- local failed_messages = {}
+ local failed = {} ---@type [integer, string][]
+ local discard = false
for k, v in pairs(on_key_cbs) do
- local ok, err_msg = pcall(v, buf, typed_buf)
+ local fn = v[1]
+ local ok, rv = xpcall(function()
+ return fn(buf, typed_buf)
+ end, debug.traceback)
+ if ok and rv ~= nil then
+ if type(rv) == 'string' and #rv == 0 then
+ discard = true
+ -- break -- Without break deliver to all callbacks even when it eventually discards.
+ -- "break" does not make sense unless callbacks are sorted by ???.
+ else
+ ok = false
+ rv = 'return string must be empty'
+ end
+ end
if not ok then
vim.on_key(nil, k)
- table.insert(failed_ns_ids, k)
- table.insert(failed_messages, err_msg)
+ table.insert(failed, { k, rv })
+ end
+ end
+
+ if #failed > 0 then
+ local errmsg = ''
+ for _, v in ipairs(failed) do
+ errmsg = errmsg .. string.format('\nWith ns_id %d: %s', v[1], v[2])
+ end
+ error(errmsg)
+ end
+ return discard
+end
+
+--- Convert UTF-32, UTF-16 or UTF-8 {index} to byte index.
+--- If {strict_indexing} is false
+--- then then an out of range index will return byte length
+--- instead of throwing an error.
+---
+--- Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|.
+--- An {index} in the middle of a UTF-16 sequence is rounded upwards to
+--- the end of that sequence.
+---@param s string
+---@param encoding "utf-8"|"utf-16"|"utf-32"
+---@param index integer
+---@param strict_indexing? boolean # default: true
+---@return integer
+function vim.str_byteindex(s, encoding, index, strict_indexing)
+ if type(encoding) == 'number' then
+ -- Legacy support for old API
+ -- Parameters: ~
+ -- • {str} (`string`)
+ -- • {index} (`integer`)
+ -- • {use_utf16} (`boolean?`)
+ vim.deprecate(
+ 'vim.str_byteindex',
+ 'vim.str_byteindex(s, encoding, index, strict_indexing)',
+ '1.0'
+ )
+ local old_index = encoding
+ local use_utf16 = index or false
+ return vim._str_byteindex(s, old_index, use_utf16) or error('index out of range')
+ end
+
+ -- Avoid vim.validate for performance.
+ if type(s) ~= 'string' or type(index) ~= 'number' then
+ vim.validate('s', s, 'string')
+ vim.validate('index', index, 'number')
+ end
+
+ local len = #s
+
+ if index == 0 or len == 0 then
+ return 0
+ end
+
+ if not utfs[encoding] then
+ vim.validate('encoding', encoding, function(v)
+ return utfs[v], 'invalid encoding'
+ end)
+ end
+
+ if strict_indexing ~= nil and type(strict_indexing) ~= 'boolean' then
+ vim.validate('strict_indexing', strict_indexing, 'boolean', true)
+ end
+ if strict_indexing == nil then
+ strict_indexing = true
+ end
+
+ if encoding == 'utf-8' then
+ if index > len then
+ return strict_indexing and error('index out of range') or len
end
+ return index
end
+ return vim._str_byteindex(s, index, encoding == 'utf-16')
+ or strict_indexing and error('index out of range')
+ or len
+end
- if failed_ns_ids[1] then
- error(
- string.format(
- "Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
- table.concat(failed_ns_ids, ', '),
- table.concat(failed_messages, '\n')
- )
+--- Convert byte index to UTF-32, UTF-16 or UTF-8 indices. If {index} is not
+--- supplied, the length of the string is used. All indices are zero-based.
+---
+--- If {strict_indexing} is false then an out of range index will return string
+--- length instead of throwing an error.
+--- Invalid UTF-8 bytes, and embedded surrogates are counted as one code point
+--- each. An {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
+--- that sequence.
+---@param s string
+---@param encoding "utf-8"|"utf-16"|"utf-32"
+---@param index? integer
+---@param strict_indexing? boolean # default: true
+---@return integer
+function vim.str_utfindex(s, encoding, index, strict_indexing)
+ if encoding == nil or type(encoding) == 'number' then
+ -- Legacy support for old API
+ -- Parameters: ~
+ -- • {str} (`string`)
+ -- • {index} (`integer?`)
+ vim.deprecate(
+ 'vim.str_utfindex',
+ 'vim.str_utfindex(s, encoding, index, strict_indexing)',
+ '1.0'
)
+ local old_index = encoding
+ local col32, col16 = vim._str_utfindex(s, old_index) --[[@as integer,integer]]
+ if not col32 or not col16 then
+ error('index out of range')
+ end
+ -- Return (multiple): ~
+ -- (`integer`) UTF-32 index
+ -- (`integer`) UTF-16 index
+ return col32, col16
+ end
+
+ if type(s) ~= 'string' or (index ~= nil and type(index) ~= 'number') then
+ vim.validate('s', s, 'string')
+ vim.validate('index', index, 'number', true)
+ end
+
+ if not index then
+ index = math.huge
+ strict_indexing = false
+ end
+
+ if index == 0 then
+ return 0
+ end
+
+ if not utfs[encoding] then
+ vim.validate('encoding', encoding, function(v)
+ return utfs[v], 'invalid encoding'
+ end)
end
+
+ if strict_indexing ~= nil and type(strict_indexing) ~= 'boolean' then
+ vim.validate('strict_indexing', strict_indexing, 'boolean', true)
+ end
+ if strict_indexing == nil then
+ strict_indexing = true
+ end
+
+ if encoding == 'utf-8' then
+ local len = #s
+ return index <= len and index or (strict_indexing and error('index out of range') or len)
+ end
+ local col32, col16 = vim._str_utfindex(s, index) --[[@as integer?,integer?]]
+ local col = encoding == 'utf-16' and col16 or col32
+ if col then
+ return col
+ end
+ if strict_indexing then
+ error('index out of range')
+ end
+ local max32, max16 = vim._str_utfindex(s)--[[@as integer integer]]
+ return encoding == 'utf-16' and max16 or max32
end
---- Generates a list of possible completions for the string.
+--- Generates a list of possible completions for the str
--- String has the pattern.
---
--- 1. Can we get it to just return things in the global namespace with that name prefix
@@ -988,21 +1151,16 @@ end
--- @param ... any
--- @return any # given arguments.
function vim.print(...)
- if vim.in_fast_event() then
- print(...)
- return ...
- end
-
+ local msg = {}
for i = 1, select('#', ...) do
local o = select(i, ...)
if type(o) == 'string' then
- vim.api.nvim_out_write(o)
+ table.insert(msg, o)
else
- vim.api.nvim_out_write(vim.inspect(o, { newline = '\n', indent = ' ' }))
+ table.insert(msg, vim.inspect(o, { newline = '\n', indent = ' ' }))
end
- vim.api.nvim_out_write('\n')
end
-
+ print(table.concat(msg, '\n'))
return ...
end
@@ -1146,16 +1304,22 @@ function vim.deprecate(name, alternative, version, plugin, backtrace)
if plugin == 'Nvim' then
require('vim.deprecated.health').add(name, version, traceback(), alternative)
- -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md.
- -- Example: if removal_version is 0.12 (soft-deprecated since 0.10-dev), show warnings starting at
- -- 0.11, including 0.11-dev
+ -- Show a warning only if feature is hard-deprecated (see MAINTAIN.md).
+ -- Example: if removal `version` is 0.12 (soft-deprecated since 0.10-dev), show warnings
+ -- starting at 0.11, including 0.11-dev.
local major, minor = version:match('(%d+)%.(%d+)')
major, minor = tonumber(major), tonumber(minor)
+ local nvim_major = 0 --- Current Nvim major version.
+
+ -- We can't "subtract" from a major version, so:
+ -- * Always treat `major > nvim_major` as soft-deprecation.
+ -- * Compare `minor - 1` if `major == nvim_major`.
+ if major > nvim_major then
+ return -- Always soft-deprecation (see MAINTAIN.md).
+ end
local hard_deprecated_since = string.format('nvim-%d.%d', major, minor - 1)
- -- Assume there will be no next minor version before bumping up the major version
- local is_hard_deprecated = minor == 0 or vim.fn.has(hard_deprecated_since) == 1
- if not is_hard_deprecated then
+ if major == nvim_major and vim.fn.has(hard_deprecated_since) == 0 then
return
end
@@ -1166,12 +1330,10 @@ function vim.deprecate(name, alternative, version, plugin, backtrace)
local displayed = vim._truncated_echo_once(msg)
return displayed and msg or nil
else
- vim.validate {
- name = { name, 'string' },
- alternative = { alternative, 'string', true },
- version = { version, 'string', true },
- plugin = { plugin, 'string', true },
- }
+ vim.validate('name', name, 'string')
+ vim.validate('alternative', alternative, 'string', true)
+ vim.validate('version', version, 'string', true)
+ vim.validate('plugin', plugin, 'string', true)
local msg = ('%s is deprecated'):format(name)
msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.')
@@ -1190,4 +1352,7 @@ require('vim._options')
---@deprecated
vim.loop = vim.uv
+-- Deprecated. Remove at Nvim 2.0
+vim.highlight = vim._defer_deprecated_module('vim.highlight', 'vim.hl')
+
return vim
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index c9f207cb20..f8672d1f1f 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -15,7 +15,7 @@ vim.fs = require('vim.fs')
vim.func = require('vim.func')
vim.glob = require('vim.glob')
vim.health = require('vim.health')
-vim.highlight = require('vim.highlight')
+vim.hl = require('vim.hl')
vim.iter = require('vim.iter')
vim.keymap = require('vim.keymap')
vim.loader = require('vim.loader')
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index c66b295d3a..3c9b9d4f44 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -592,8 +592,9 @@ function vim.api.nvim_buf_line_count(buffer) end
--- - id : id of the extmark to edit.
--- - end_row : ending line of the mark, 0-based inclusive.
--- - end_col : ending col of the mark, 0-based exclusive.
---- - hl_group : name of the highlight group used to highlight
---- this mark.
+--- - hl_group : highlight group used for the text range. This and below
+--- highlight groups can be supplied either as a string or as an integer,
+--- the latter of which can be obtained using `nvim_get_hl_id_by_name()`.
--- - hl_eol : when true, for a multiline highlight covering the
--- EOL of a line, continue the highlight for the rest
--- of the screen line (just like for diff and
@@ -603,9 +604,7 @@ function vim.api.nvim_buf_line_count(buffer) end
--- text chunk with specified highlight. `highlight` element
--- can either be a single highlight group, or an array of
--- multiple highlight groups that will be stacked
---- (highest priority last). A highlight group can be supplied
---- either as a string or as an integer, the latter which
---- can be obtained using `nvim_get_hl_id_by_name()`.
+--- (highest priority last).
--- - virt_text_pos : position of virtual text. Possible values:
--- - "eol": right after eol character (default).
--- - "overlay": display over the specified column, without
@@ -676,15 +675,12 @@ function vim.api.nvim_buf_line_count(buffer) end
--- buffer or end of the line respectively. Defaults to true.
--- - sign_text: string of length 1-2 used to display in the
--- sign column.
---- - sign_hl_group: name of the highlight group used to
---- highlight the sign column text.
---- - number_hl_group: name of the highlight group used to
---- highlight the number column.
---- - line_hl_group: name of the highlight group used to
---- highlight the whole line.
---- - cursorline_hl_group: name of the highlight group used to
---- highlight the sign column text when the cursor is on
---- the same line as the mark and 'cursorline' is enabled.
+--- - sign_hl_group: highlight group used for the sign column text.
+--- - number_hl_group: highlight group used for the number column.
+--- - line_hl_group: highlight group used for the whole line.
+--- - cursorline_hl_group: highlight group used for the sign
+--- column text when the cursor is on the same line as the
+--- mark and 'cursorline' is enabled.
--- - conceal: string which should be either empty or a single
--- character. Enable concealing similar to `:syn-conceal`.
--- When a character is supplied it is used as `:syn-cchar`.
@@ -1106,12 +1102,12 @@ function vim.api.nvim_del_var(name) end
--- Echo a message.
---
--- @param chunks any[] A list of `[text, hl_group]` arrays, each representing a
---- text chunk with specified highlight. `hl_group` element
---- can be omitted for no highlight.
+--- text chunk with specified highlight group name or ID.
+--- `hl_group` element can be omitted for no highlight.
--- @param history boolean if true, add to `message-history`.
--- @param opts vim.api.keyset.echo_opts Optional parameters.
---- - verbose: Message was printed as a result of 'verbose' option
---- if Nvim was invoked with -V3log_file, the message will be
+--- - verbose: Message is printed as a result of 'verbose' option.
+--- If Nvim was invoked with -V3log_file, the message will be
--- redirected to the log_file and suppressed from direct output.
function vim.api.nvim_echo(chunks, history, opts) end
@@ -1767,7 +1763,12 @@ function vim.api.nvim_open_term(buffer, opts) end
--- fractional.
--- - focusable: Enable focus by user actions (wincmds, mouse events).
--- Defaults to true. Non-focusable windows can be entered by
---- `nvim_set_current_win()`.
+--- `nvim_set_current_win()`, or, when the `mouse` field is set to true,
+--- by mouse events. See `focusable`.
+--- - mouse: Specify how this window interacts with mouse events.
+--- Defaults to `focusable` value.
+--- - If false, mouse events pass through this window.
+--- - If true, mouse events interact with this window normally.
--- - external: GUI should display the window as an external
--- top-level window. Currently accepts no other positioning
--- configuration together with this.
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index 2fe5c32faf..bf184dee2d 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -295,6 +295,7 @@ error('Cannot require a meta file')
--- @field bufpos? any[]
--- @field external? boolean
--- @field focusable? boolean
+--- @field mouse? boolean
--- @field vertical? boolean
--- @field zindex? integer
--- @field border? any
diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
index 13bd1c1294..b8779b66fe 100644
--- a/runtime/lua/vim/_meta/builtin.lua
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -112,18 +112,6 @@ function vim.rpcrequest(channel, method, ...) end
--- equal, {a} is greater than {b} or {a} is lesser than {b}, respectively.
function vim.stricmp(a, b) end
---- Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not
---- supplied, it defaults to false (use UTF-32). Returns the byte index.
----
---- Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|.
---- An {index} in the middle of a UTF-16 sequence is rounded upwards to
---- the end of that sequence.
---- @param str string
---- @param index integer
---- @param use_utf16? boolean
---- @return integer
-function vim.str_byteindex(str, index, use_utf16) end
-
--- Gets a list of the starting byte positions of each UTF-8 codepoint in the given string.
---
--- Embedded NUL bytes are treated as terminating the string.
@@ -173,19 +161,6 @@ function vim.str_utf_start(str, index) end
--- @return integer
function vim.str_utf_end(str, index) end
---- Convert byte index to UTF-32 and UTF-16 indices. If {index} is not
---- supplied, the length of the string is used. All indices are zero-based.
----
---- Embedded NUL bytes are treated as terminating the string. Invalid UTF-8
---- bytes, and embedded surrogates are counted as one code point each. An
---- {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
---- that sequence.
---- @param str string
---- @param index? integer
---- @return integer # UTF-32 index
---- @return integer # UTF-16 index
-function vim.str_utfindex(str, index) end
-
--- The result is a String, which is the text {str} converted from
--- encoding {from} to encoding {to}. When the conversion fails `nil` is
--- returned. When some characters could not be converted they
@@ -258,6 +233,12 @@ function vim.wait(time, callback, interval, fast_only) end
--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
--- and the sections below for event format for respective events.
---
+--- Callbacks for `msg_show` events are executed in |api-fast| context unless
+--- Nvim will wait for input, in which case messages should be shown
+--- immediately.
+---
+--- Excessive errors inside the callback will result in forced detachment.
+---
--- WARNING: This api is considered experimental. Usability will vary for
--- different screen elements. In particular `ext_messages` behavior is subject
--- to further changes and usability improvements. This is expected to be
diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua
index aca6649957..eae76d80d7 100644
--- a/runtime/lua/vim/_meta/builtin_types.lua
+++ b/runtime/lua/vim/_meta/builtin_types.lua
@@ -66,6 +66,97 @@
--- @field winnr integer
--- @field winrow integer
+--- @class vim.quickfix.entry
+--- buffer number; must be the number of a valid buffer
+--- @field bufnr? integer
+---
+--- name of a file; only used when "bufnr" is not
+--- present or it is invalid.
+--- @field filename? string
+---
+--- name of a module; if given it will be used in
+--- quickfix error window instead of the filename.
+--- @field module? string
+---
+--- line number in the file
+--- @field lnum? integer
+---
+--- end of lines, if the item spans multiple lines
+--- @field end_lnum? integer
+---
+--- search pattern used to locate the error
+--- @field pattern? string
+---
+--- column number
+--- @field col? integer
+---
+--- when non-zero: "col" is visual column
+--- when zero: "col" is byte index
+--- @field vcol? integer
+---
+--- end column, if the item spans multiple columns
+--- @field end_col? integer
+---
+--- error number
+--- @field nr? integer
+---
+--- description of the error
+--- @field text? string
+---
+--- single-character error type, 'E', 'W', etc.
+--- @field type? string
+---
+--- recognized error message
+--- @field valid? boolean
+---
+--- custom data associated with the item, can be
+--- any type.
+--- @field user_data? any
+
+--- @class vim.fn.setqflist.what
+---
+--- quickfix list context. See |quickfix-context|
+--- @field context? table
+---
+--- errorformat to use when parsing text from
+--- "lines". If this is not present, then the
+--- 'errorformat' option value is used.
+--- See |quickfix-parse|
+--- @field efm? string
+---
+--- quickfix list identifier |quickfix-ID|
+--- @field id? integer
+--- index of the current entry in the quickfix
+--- list specified by "id" or "nr". If set to '$',
+--- then the last entry in the list is set as the
+--- current entry. See |quickfix-index|
+--- @field idx? integer
+---
+--- list of quickfix entries. Same as the {list}
+--- argument.
+--- @field items? vim.quickfix.entry[]
+---
+--- use 'errorformat' to parse a list of lines and
+--- add the resulting entries to the quickfix list
+--- {nr} or {id}. Only a |List| value is supported.
+--- See |quickfix-parse|
+--- @field lines? string[]
+---
+--- list number in the quickfix stack; zero
+--- means the current quickfix list and "$" means
+--- the last quickfix list.
+--- @field nr? integer
+---
+--- function to get the text to display in the
+--- quickfix window. The value can be the name of
+--- a function or a funcref or a lambda. Refer
+--- to |quickfix-window-function| for an explanation
+--- of how to write the function and an example.
+--- @field quickfixtextfunc? function
+---
+--- quickfix list title text. See |quickfix-title|
+--- @field title? string
+
--- @class vim.fn.sign_define.dict
--- @field text string
--- @field icon? string
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index ce3ff4f861..cb783720ac 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -289,12 +289,13 @@ vim.go.bk = vim.go.backup
--- useful for example in source trees where all the files are symbolic or
--- hard links and any changes should stay in the local source tree, not
--- be propagated back to the original source.
---- *crontab*
+--- *crontab*
--- One situation where "no" and "auto" will cause problems: A program
--- that opens a file, invokes Vim to edit that file, and then tests if
--- the open file was changed (through the file descriptor) will check the
--- backup file instead of the newly created file. "crontab -e" is an
---- example.
+--- example, as are several `file-watcher` daemons like inotify. In that
+--- case you probably want to switch this option.
---
--- When a copy is made, the original file is truncated and then filled
--- with the new text. This means that protection bits, owner and
@@ -429,6 +430,7 @@ vim.go.bsk = vim.go.backupskip
--- separated list of items. For each item that is present, the bell
--- will be silenced. This is most useful to specify specific events in
--- insert mode to be silenced.
+--- You can also make it flash by using 'visualbell'.
---
--- item meaning when present ~
--- all All events.
@@ -452,6 +454,7 @@ vim.go.bsk = vim.go.backupskip
--- register Unknown register after <C-R> in `Insert-mode`.
--- shell Bell from shell output `:!`.
--- spell Error happened on spell suggest.
+--- term Bell from `:terminal` output.
--- wildmode More matches in `cmdline-completion` available
--- (depends on the 'wildmode' setting).
---
@@ -572,19 +575,6 @@ vim.o.briopt = vim.o.breakindentopt
vim.wo.breakindentopt = vim.o.breakindentopt
vim.wo.briopt = vim.wo.breakindentopt
---- Which directory to use for the file browser:
---- last Use same directory as with last file browser, where a
---- file was opened or saved.
---- buffer Use the directory of the related buffer.
---- current Use the current directory.
---- {path} Use the specified directory
----
---- @type string
-vim.o.browsedir = ""
-vim.o.bsdir = vim.o.browsedir
-vim.go.browsedir = vim.o.browsedir
-vim.go.bsdir = vim.go.browsedir
-
--- This option specifies what happens when a buffer is no longer
--- displayed in a window:
--- <empty> follow the global 'hidden' option
@@ -1116,7 +1106,7 @@ vim.bo.cot = vim.bo.completeopt
vim.go.completeopt = vim.o.completeopt
vim.go.cot = vim.go.completeopt
---- only for MS-Windows
+--- only modifiable in MS-Windows
--- When this option is set it overrules 'shellslash' for completion:
--- - When this option is set to "slash", a forward slash is used for path
--- completion in insert mode. This is useful when editing HTML tag, or
@@ -2304,6 +2294,62 @@ vim.wo.fcs = vim.wo.fillchars
vim.go.fillchars = vim.o.fillchars
vim.go.fcs = vim.go.fillchars
+--- Function that is called to obtain the filename(s) for the `:find`
+--- command. When this option is empty, the internal `file-searching`
+--- mechanism is used.
+---
+--- The value can be the name of a function, a `lambda` or a `Funcref`.
+--- See `option-value-function` for more information.
+---
+--- The function is called with two arguments. The first argument is a
+--- `String` and is the `:find` command argument. The second argument is
+--- a `Boolean` and is set to `v:true` when the function is called to get
+--- a List of command-line completion matches for the `:find` command.
+--- The function should return a List of strings.
+---
+--- The function is called only once per `:find` command invocation.
+--- The function can process all the directories specified in 'path'.
+---
+--- If a match is found, the function should return a `List` containing
+--- one or more file names. If a match is not found, the function
+--- should return an empty List.
+---
+--- If any errors are encountered during the function invocation, an
+--- empty List is used as the return value.
+---
+--- It is not allowed to change text or jump to another window while
+--- executing the 'findfunc' `textlock`.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- Examples:
+---
+--- ```vim
+--- " Use glob()
+--- func FindFuncGlob(cmdarg, cmdcomplete)
+--- let pat = a:cmdcomplete ? $'{a:cmdarg}*' : a:cmdarg
+--- return glob(pat, v:false, v:true)
+--- endfunc
+--- set findfunc=FindFuncGlob
+---
+--- " Use the 'git ls-files' output
+--- func FindGitFiles(cmdarg, cmdcomplete)
+--- let fnames = systemlist('git ls-files')
+--- return fnames->filter('v:val =~? a:cmdarg')
+--- endfunc
+--- set findfunc=FindGitFiles
+--- ```
+---
+---
+--- @type string
+vim.o.findfunc = ""
+vim.o.ffu = vim.o.findfunc
+vim.bo.findfunc = vim.o.findfunc
+vim.bo.ffu = vim.bo.findfunc
+vim.go.findfunc = vim.o.findfunc
+vim.go.ffu = vim.go.findfunc
+
--- When writing a file and this option is on, <EOL> at the end of file
--- will be restored if missing. Turn this option off if you want to
--- preserve the situation from the original file.
@@ -2897,148 +2943,6 @@ vim.o.gfw = vim.o.guifontwide
vim.go.guifontwide = vim.o.guifontwide
vim.go.gfw = vim.go.guifontwide
---- This option only has an effect in the GUI version of Vim. It is a
---- sequence of letters which describes what components and options of the
---- GUI should be used.
---- To avoid problems with flags that are added in the future, use the
---- "+=" and "-=" feature of ":set" `add-option-flags`.
----
---- Valid letters are as follows:
---- *guioptions_a* *'go-a'*
---- 'a' Autoselect: If present, then whenever VISUAL mode is started,
---- or the Visual area extended, Vim tries to become the owner of
---- the windowing system's global selection. This means that the
---- Visually highlighted text is available for pasting into other
---- applications as well as into Vim itself. When the Visual mode
---- ends, possibly due to an operation on the text, or when an
---- application wants to paste the selection, the highlighted text
---- is automatically yanked into the "* selection register.
---- Thus the selection is still available for pasting into other
---- applications after the VISUAL mode has ended.
---- If not present, then Vim won't become the owner of the
---- windowing system's global selection unless explicitly told to
---- by a yank or delete operation for the "* register.
---- The same applies to the modeless selection.
---- *'go-P'*
---- 'P' Like autoselect but using the "+ register instead of the "*
---- register.
---- *'go-A'*
---- 'A' Autoselect for the modeless selection. Like 'a', but only
---- applies to the modeless selection.
----
---- 'guioptions' autoselect Visual autoselect modeless ~
---- "" - -
---- "a" yes yes
---- "A" - yes
---- "aA" yes yes
----
---- *'go-c'*
---- 'c' Use console dialogs instead of popup dialogs for simple
---- choices.
---- *'go-d'*
---- 'd' Use dark theme variant if available.
---- *'go-e'*
---- 'e' Add tab pages when indicated with 'showtabline'.
---- 'guitablabel' can be used to change the text in the labels.
---- When 'e' is missing a non-GUI tab pages line may be used.
---- The GUI tabs are only supported on some systems, currently
---- Mac OS/X and MS-Windows.
---- *'go-i'*
---- 'i' Use a Vim icon.
---- *'go-m'*
---- 'm' Menu bar is present.
---- *'go-M'*
---- 'M' The system menu "$VIMRUNTIME/menu.vim" is not sourced. Note
---- that this flag must be added in the vimrc file, before
---- switching on syntax or filetype recognition (when the `gvimrc`
---- file is sourced the system menu has already been loaded; the
---- `:syntax on` and `:filetype on` commands load the menu too).
---- *'go-g'*
---- 'g' Grey menu items: Make menu items that are not active grey. If
---- 'g' is not included inactive menu items are not shown at all.
---- *'go-T'*
---- 'T' Include Toolbar. Currently only in Win32 GUI.
---- *'go-r'*
---- 'r' Right-hand scrollbar is always present.
---- *'go-R'*
---- 'R' Right-hand scrollbar is present when there is a vertically
---- split window.
---- *'go-l'*
---- 'l' Left-hand scrollbar is always present.
---- *'go-L'*
---- 'L' Left-hand scrollbar is present when there is a vertically
---- split window.
---- *'go-b'*
---- 'b' Bottom (horizontal) scrollbar is present. Its size depends on
---- the longest visible line, or on the cursor line if the 'h'
---- flag is included. `gui-horiz-scroll`
---- *'go-h'*
---- 'h' Limit horizontal scrollbar size to the length of the cursor
---- line. Reduces computations. `gui-horiz-scroll`
----
---- And yes, you may even have scrollbars on the left AND the right if
---- you really want to :-). See `gui-scrollbars` for more information.
----
---- *'go-v'*
---- 'v' Use a vertical button layout for dialogs. When not included,
---- a horizontal layout is preferred, but when it doesn't fit a
---- vertical layout is used anyway. Not supported in GTK 3.
---- *'go-p'*
---- 'p' Use Pointer callbacks for X11 GUI. This is required for some
---- window managers. If the cursor is not blinking or hollow at
---- the right moment, try adding this flag. This must be done
---- before starting the GUI. Set it in your `gvimrc`. Adding or
---- removing it after the GUI has started has no effect.
---- *'go-k'*
---- 'k' Keep the GUI window size when adding/removing a scrollbar, or
---- toolbar, tabline, etc. Instead, the behavior is similar to
---- when the window is maximized and will adjust 'lines' and
---- 'columns' to fit to the window. Without the 'k' flag Vim will
---- try to keep 'lines' and 'columns' the same when adding and
---- removing GUI components.
----
---- @type string
-vim.o.guioptions = ""
-vim.o.go = vim.o.guioptions
-vim.go.guioptions = vim.o.guioptions
-vim.go.go = vim.go.guioptions
-
---- When non-empty describes the text to use in a label of the GUI tab
---- pages line. When empty and when the result is empty Vim will use a
---- default label. See `setting-guitablabel` for more info.
----
---- The format of this option is like that of 'statusline'.
---- 'guitabtooltip' is used for the tooltip, see below.
---- The expression will be evaluated in the `sandbox` when set from a
---- modeline, see `sandbox-option`.
---- This option cannot be set in a modeline when 'modelineexpr' is off.
----
---- Only used when the GUI tab pages line is displayed. 'e' must be
---- present in 'guioptions'. For the non-GUI tab pages line 'tabline' is
---- used.
----
---- @type string
-vim.o.guitablabel = ""
-vim.o.gtl = vim.o.guitablabel
-vim.go.guitablabel = vim.o.guitablabel
-vim.go.gtl = vim.go.guitablabel
-
---- When non-empty describes the text to use in a tooltip for the GUI tab
---- pages line. When empty Vim will use a default tooltip.
---- This option is otherwise just like 'guitablabel' above.
---- You can include a line break. Simplest method is to use `:let`:
----
---- ```vim
---- let &guitabtooltip = "line one\nline two"
---- ```
----
----
---- @type string
-vim.o.guitabtooltip = ""
-vim.o.gtt = vim.o.guitabtooltip
-vim.go.guitabtooltip = vim.o.guitabtooltip
-vim.go.gtt = vim.go.guitabtooltip
-
--- Name of the main help file. All distributed help files should be
--- placed together in one directory. Additionally, all "doc" directories
--- in 'runtimepath' will be used.
@@ -3112,7 +3016,8 @@ vim.go.hid = vim.go.hidden
--- A history of ":" commands, and a history of previous search patterns
--- is remembered. This option decides how many entries may be stored in
---- each of these histories (see `cmdline-editing`).
+--- each of these histories (see `cmdline-editing` and 'msghistory' for
+--- the number of messages to remember).
--- The maximum value is 10000.
---
--- @type integer
@@ -3181,29 +3086,6 @@ vim.o.ic = vim.o.ignorecase
vim.go.ignorecase = vim.o.ignorecase
vim.go.ic = vim.go.ignorecase
---- When set the Input Method is always on when starting to edit a command
---- line, unless entering a search pattern (see 'imsearch' for that).
---- Setting this option is useful when your input method allows entering
---- English characters directly, e.g., when it's used to type accented
---- characters with dead keys.
----
---- @type boolean
-vim.o.imcmdline = false
-vim.o.imc = vim.o.imcmdline
-vim.go.imcmdline = vim.o.imcmdline
-vim.go.imc = vim.go.imcmdline
-
---- When set the Input Method is never used. This is useful to disable
---- the IM when it doesn't work properly.
---- Currently this option is on by default for SGI/IRIX machines. This
---- may change in later releases.
----
---- @type boolean
-vim.o.imdisable = false
-vim.o.imd = vim.o.imdisable
-vim.go.imdisable = vim.o.imdisable
-vim.go.imd = vim.go.imdisable
-
--- Specifies whether :lmap or an Input Method (IM) is to be used in
--- Insert mode. Valid values:
--- 0 :lmap is off and IM is off
@@ -4488,74 +4370,6 @@ vim.go.mousemev = vim.go.mousemoveevent
vim.o.mousescroll = "ver:3,hor:6"
vim.go.mousescroll = vim.o.mousescroll
---- This option tells Vim what the mouse pointer should look like in
---- different modes. The option is a comma-separated list of parts, much
---- like used for 'guicursor'. Each part consist of a mode/location-list
---- and an argument-list:
---- mode-list:shape,mode-list:shape,..
---- The mode-list is a dash separated list of these modes/locations:
---- In a normal window: ~
---- n Normal mode
---- v Visual mode
---- ve Visual mode with 'selection' "exclusive" (same as 'v',
---- if not specified)
---- o Operator-pending mode
---- i Insert mode
---- r Replace mode
----
---- Others: ~
---- c appending to the command-line
---- ci inserting in the command-line
---- cr replacing in the command-line
---- m at the 'Hit ENTER' or 'More' prompts
---- ml idem, but cursor in the last line
---- e any mode, pointer below last window
---- s any mode, pointer on a status line
---- sd any mode, while dragging a status line
---- vs any mode, pointer on a vertical separator line
---- vd any mode, while dragging a vertical separator line
---- a everywhere
----
---- The shape is one of the following:
---- avail name looks like ~
---- w x arrow Normal mouse pointer
---- w x blank no pointer at all (use with care!)
---- w x beam I-beam
---- w x updown up-down sizing arrows
---- w x leftright left-right sizing arrows
---- w x busy The system's usual busy pointer
---- w x no The system's usual "no input" pointer
---- x udsizing indicates up-down resizing
---- x lrsizing indicates left-right resizing
---- x crosshair like a big thin +
---- x hand1 black hand
---- x hand2 white hand
---- x pencil what you write with
---- x question big ?
---- x rightup-arrow arrow pointing right-up
---- w x up-arrow arrow pointing up
---- x <number> any X11 pointer number (see X11/cursorfont.h)
----
---- The "avail" column contains a 'w' if the shape is available for Win32,
---- x for X11.
---- Any modes not specified or shapes not available use the normal mouse
---- pointer.
----
---- Example:
----
---- ```vim
---- set mouseshape=s:udsizing,m:no
---- ```
---- will make the mouse turn to a sizing arrow over the status lines and
---- indicate no input when the hit-enter prompt is displayed (since
---- clicking the mouse has no effect in this state.)
----
---- @type string
-vim.o.mouseshape = ""
-vim.o.mouses = vim.o.mouseshape
-vim.go.mouseshape = vim.o.mouseshape
-vim.go.mouses = vim.go.mouseshape
-
--- Defines the maximum time in msec between two mouse clicks for the
--- second click to be recognized as a multi click.
---
@@ -4565,6 +4379,15 @@ vim.o.mouset = vim.o.mousetime
vim.go.mousetime = vim.o.mousetime
vim.go.mouset = vim.go.mousetime
+--- Determines how many entries are remembered in the `:messages` history.
+--- The maximum value is 10000.
+---
+--- @type integer
+vim.o.msghistory = 500
+vim.o.mhi = vim.o.msghistory
+vim.go.msghistory = vim.o.msghistory
+vim.go.mhi = vim.go.msghistory
+
--- This defines what bases Vim will consider for numbers when using the
--- CTRL-A and CTRL-X commands for adding to and subtracting from a number
--- respectively; see `CTRL-A` for more info on these commands.
@@ -4675,19 +4498,6 @@ vim.o.ofu = vim.o.omnifunc
vim.bo.omnifunc = vim.o.omnifunc
vim.bo.ofu = vim.bo.omnifunc
---- only for Windows
---- Enable reading and writing from devices. This may get Vim stuck on a
---- device that can be opened but doesn't actually do the I/O. Therefore
---- it is off by default.
---- Note that on Windows editing "aux.h", "lpt1.txt" and the like also
---- result in editing a device.
----
---- @type boolean
-vim.o.opendevice = false
-vim.o.odev = vim.o.opendevice
-vim.go.opendevice = vim.o.opendevice
-vim.go.odev = vim.go.opendevice
-
--- This option specifies a function to be called by the `g@` operator.
--- See `:map-operator` for more info and an example. The value can be
--- the name of a function, a `lambda` or a `Funcref`. See
@@ -5634,7 +5444,7 @@ vim.go.sdf = vim.go.shadafile
---
--- ```vim
--- let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
---- let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
+--- let &shellcmdflag = '-NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';$PSStyle.OutputRendering=''plaintext'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
--- let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
--- let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
--- set shellquote= shellxquote=
@@ -5747,7 +5557,7 @@ vim.o.srr = vim.o.shellredir
vim.go.shellredir = vim.o.shellredir
vim.go.srr = vim.go.shellredir
---- only for MS-Windows
+--- only modifiable in MS-Windows
--- When set, a forward slash is used when expanding file names. This is
--- useful when a Unix-like shell is used instead of cmd.exe. Backward
--- slashes can still be typed, but they are changed to forward slashes by
@@ -5764,7 +5574,7 @@ vim.go.srr = vim.go.shellredir
--- Also see 'completeslash'.
---
--- @type boolean
-vim.o.shellslash = false
+vim.o.shellslash = true
vim.o.ssl = vim.o.shellslash
vim.go.shellslash = vim.o.shellslash
vim.go.ssl = vim.go.shellslash
@@ -6695,7 +6505,7 @@ vim.wo.stc = vim.wo.statuscolumn
--- Emulate standard status line with 'ruler' set
---
--- ```vim
---- set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+--- set statusline=%<%f\ %h%w%m%r%=%-14.(%l,%c%V%)\ %P
--- ```
--- Similar, but add ASCII value of char under the cursor (like "ga")
---
@@ -7309,7 +7119,9 @@ vim.go.titleold = vim.o.titleold
--- window. This happens only when the 'title' option is on.
---
--- When this option contains printf-style '%' items, they will be
---- expanded according to the rules used for 'statusline'.
+--- expanded according to the rules used for 'statusline'. If it contains
+--- an invalid '%' format, the value is used as-is and no error or warning
+--- will be given when the value is set.
--- This option cannot be set in a modeline when 'modelineexpr' is off.
---
--- Example:
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 3f6deba092..5eb15e1eee 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -221,16 +221,16 @@ function vim.fn.assert_beeps(cmd) end
--- @return 0|1
function vim.fn.assert_equal(expected, actual, msg) end
---- When the files {fname-one} and {fname-two} do not contain
+--- When the files {fname_one} and {fname_two} do not contain
--- exactly the same text an error message is added to |v:errors|.
--- Also see |assert-return|.
---- When {fname-one} or {fname-two} does not exist the error will
+--- When {fname_one} or {fname_two} does not exist the error will
--- mention that.
---
---- @param fname-one string
---- @param fname-two string
+--- @param fname_one string
+--- @param fname_two string
--- @return 0|1
-function vim.fn.assert_equalfile(fname-one, fname-two) end
+function vim.fn.assert_equalfile(fname_one, fname_two) end
--- When v:exception does not contain the string {error} an error
--- message is added to |v:errors|. Also see |assert-return|.
@@ -809,7 +809,7 @@ function vim.fn.char2nr(string, utf8) end
--- The character class is one of:
--- 0 blank
--- 1 punctuation
---- 2 word character
+--- 2 word character (depends on 'iskeyword')
--- 3 emoji
--- other specific Unicode class
--- The class is used in patterns and word motions.
@@ -828,7 +828,7 @@ function vim.fn.charclass(string) end
--- echo col('.') " returns 7
--- <
---
---- @param expr string|integer[]
+--- @param expr string|any[]
--- @param winid? integer
--- @return integer
function vim.fn.charcol(expr, winid) end
@@ -956,7 +956,7 @@ function vim.fn.clearmatches(win) end
--- imap <F2> <Cmd>echo col(".").."\n"<CR>
--- <
---
---- @param expr string|integer[]
+--- @param expr string|any[]
--- @param winid? integer
--- @return integer
function vim.fn.col(expr, winid) end
@@ -2879,12 +2879,22 @@ function vim.fn.getcharsearch() end
--- @return string
function vim.fn.getcharstr(expr) end
+--- Return completion pattern of the current command-line.
+--- Only works when the command line is being edited, thus
+--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
+--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
+--- |getcmdprompt()|, |getcmdcompltype()| and |setcmdline()|.
+--- Returns an empty string when completion is not defined.
+---
+--- @return string
+function vim.fn.getcmdcomplpat() end
+
--- Return the type of the current command-line completion.
--- Only works when the command line is being edited, thus
--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
--- See |:command-completion| for the return string.
--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
---- |getcmdprompt()| and |setcmdline()|.
+--- |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|.
--- Returns an empty string when completion is not defined.
---
--- @return string
@@ -2998,6 +3008,7 @@ function vim.fn.getcmdwintype() end
--- runtime |:runtime| completion
--- scriptnames sourced script names |:scriptnames|
--- shellcmd Shell command
+--- shellcmdline Shell command line with filename arguments
--- sign |:sign| suboptions
--- syntax syntax file names |'syntax'|
--- syntime |:syntime| suboptions
@@ -5181,7 +5192,7 @@ function vim.fn.log(expr) end
function vim.fn.log10(expr) end
--- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
---- When {expr1} is a |List|| or |Dictionary|, replace each
+--- When {expr1} is a |List| or |Dictionary|, replace each
--- item in {expr1} with the result of evaluating {expr2}.
--- For a |Blob| each byte is replaced.
--- For a |String|, each character, including composing
@@ -7912,7 +7923,7 @@ function vim.fn.setbufvar(buf, varname, val) end
--- To clear the overrides pass an empty {list}: >vim
--- call setcellwidths([])
---
---- <You can use the script $VIMRUNTIME/tools/emoji_list.lua to see
+--- <You can use the script $VIMRUNTIME/scripts/emoji_list.lua to see
--- the effect for known emoji characters. Move the cursor
--- through the text to check if the cell widths of your terminal
--- match with what Vim knows about each emoji. If it doesn't
@@ -8218,6 +8229,8 @@ function vim.fn.setpos(expr, list) end
--- clear the list: >vim
--- call setqflist([], 'r')
--- <
+--- 'u' Like 'r', but tries to preserve the current selection
+--- in the quickfix list.
--- 'f' All the quickfix lists in the quickfix stack are
--- freed.
---
@@ -8273,9 +8286,9 @@ function vim.fn.setpos(expr, list) end
--- independent of the 'errorformat' setting. Use a command like
--- `:cc 1` to jump to the first position.
---
---- @param list any[]
+--- @param list vim.quickfix.entry[]
--- @param action? string
---- @param what? table
+--- @param what? vim.fn.setqflist.what
--- @return any
function vim.fn.setqflist(list, action, what) end
@@ -10533,7 +10546,7 @@ function vim.fn.values(dict) end
--- echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
--- <
---
---- @param expr string|integer[]
+--- @param expr string|any[]
--- @param list? boolean
--- @param winid? integer
--- @return any
@@ -10616,7 +10629,7 @@ function vim.fn.wait(timeout, condition, interval) end
--- For example to make <c-j> work like <down> in wildmode, use: >vim
--- cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
--- <
---- (Note, this needs the 'wildcharm' option set appropriately).
+--- (Note: this needs the 'wildcharm' option set appropriately).
---
--- @return any
function vim.fn.wildmenumode() end
diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua
index e00402ab3f..8784fdbac9 100644
--- a/runtime/lua/vim/_meta/vvars.lua
+++ b/runtime/lua/vim/_meta/vvars.lua
@@ -160,13 +160,14 @@ vim.v.errors = ...
--- an aborting condition (e.g. `c_Esc` or
--- `c_CTRL-C` for `CmdlineLeave`).
--- chan `channel-id`
+--- info Dict of arbitrary event data.
--- cmdlevel Level of cmdline.
--- cmdtype Type of cmdline, `cmdline-char`.
--- cwd Current working directory.
--- inclusive Motion is `inclusive`, else exclusive.
--- scope Event-specific scope name.
--- operator Current `operator`. Also set for Ex
---- commands (unlike `v:operator`). For
+--- commands (unlike `v:operator`). For
--- example if `TextYankPost` is triggered
--- by the `:yank` Ex command then
--- `v:event.operator` is "y".
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index a61fa61256..77d7054626 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -274,11 +274,9 @@ vim.go = setmetatable({}, {
})
--- Get or set buffer-scoped |options| for the buffer with number {bufnr}.
---- If {bufnr} is omitted then the current buffer is used.
+--- Like `:setlocal`. If {bufnr} is omitted then the current buffer is used.
--- Invalid {bufnr} or key is an error.
---
---- Note: this is equivalent to `:setlocal` for |global-local| options and `:set` otherwise.
----
--- Example:
---
--- ```lua
diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
index d603971495..ce5dbffeaa 100644
--- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -230,6 +230,8 @@ local function default_handler(stream, text, bucket)
end
end
+local is_win = vim.fn.has('win32') == 1
+
local M = {}
--- @param cmd string
@@ -238,6 +240,13 @@ local M = {}
--- @param on_error fun()
--- @return uv.uv_process_t, integer
local function spawn(cmd, opts, on_exit, on_error)
+ if is_win then
+ local cmd1 = vim.fn.exepath(cmd)
+ if cmd1 ~= '' then
+ cmd = cmd1
+ end
+ end
+
local handle, pid_or_err = uv.spawn(cmd, opts, on_exit)
if not handle then
on_error()
@@ -309,11 +318,9 @@ end
--- @param on_exit? fun(out: vim.SystemCompleted)
--- @return vim.SystemObj
function M.run(cmd, opts, on_exit)
- vim.validate({
- cmd = { cmd, 'table' },
- opts = { opts, 'table', true },
- on_exit = { on_exit, 'function', true },
- })
+ vim.validate('cmd', cmd, 'table')
+ vim.validate('opts', opts, 'table', true)
+ vim.validate('on_exit', on_exit, 'function', true)
opts = opts or {}
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index 11f6742941..13894c6147 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -59,11 +59,9 @@ end
--- @param callback vim._watch.Callback Callback for new events
--- @return fun() cancel Stops the watcher
function M.watch(path, opts, callback)
- vim.validate({
- path = { path, 'string', false },
- opts = { opts, 'table', true },
- callback = { callback, 'function', false },
- })
+ vim.validate('path', path, 'string')
+ vim.validate('opts', opts, 'table', true)
+ vim.validate('callback', callback, 'function')
opts = opts or {}
@@ -127,11 +125,9 @@ end
--- @param callback vim._watch.Callback Callback for new events
--- @return fun() cancel Stops the watcher
function M.watchdirs(path, opts, callback)
- vim.validate({
- path = { path, 'string', false },
- opts = { opts, 'table', true },
- callback = { callback, 'function', false },
- })
+ vim.validate('path', path, 'string')
+ vim.validate('opts', opts, 'table', true)
+ vim.validate('callback', callback, 'function')
opts = opts or {}
local debounce = opts.debounce or 500
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index ef00a1fa51..4fb8c6a686 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -71,9 +71,9 @@ local M = {}
--- (default: `false`)
--- @field update_in_insert? boolean
---
---- Sort diagnostics by severity. This affects the order in which signs and
---- virtual text are displayed. When true, higher severities are displayed
---- before lower severities (e.g. ERROR is displayed before WARN).
+--- Sort diagnostics by severity. This affects the order in which signs,
+--- virtual text, and highlights are displayed. When true, higher severities are
+--- displayed before lower severities (e.g. ERROR is displayed before WARN).
--- Options:
--- - {reverse}? (boolean) Reverse sort order
--- (default: `false`)
@@ -282,6 +282,11 @@ M.severity = {
[2] = 'WARN',
[3] = 'INFO',
[4] = 'HINT',
+ --- Mappings from qflist/loclist error types to severities
+ E = 1,
+ W = 2,
+ I = 3,
+ N = 4,
}
--- @alias vim.diagnostic.SeverityInt 1|2|3|4
@@ -289,12 +294,6 @@ M.severity = {
--- See |diagnostic-severity| and |vim.diagnostic.get()|
--- @alias vim.diagnostic.SeverityFilter vim.diagnostic.Severity|vim.diagnostic.Severity[]|{min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
--- Mappings from qflist/loclist error types to severities
-M.severity.E = M.severity.ERROR
-M.severity.W = M.severity.WARN
-M.severity.I = M.severity.INFO
-M.severity.N = M.severity.HINT
-
--- @type vim.diagnostic.Opts
local global_diagnostic_options = {
signs = true,
@@ -320,7 +319,7 @@ local global_diagnostic_options = {
--- @type table<string,vim.diagnostic.Handler>
M.handlers = setmetatable({}, {
__newindex = function(t, name, handler)
- vim.validate({ handler = { handler, 't' } })
+ vim.validate('handler', handler, 'table')
rawset(t, name, handler)
if global_diagnostic_options[name] == nil then
global_diagnostic_options[name] = true
@@ -477,10 +476,8 @@ end
--- @param diagnostics vim.Diagnostic[]
--- @return vim.Diagnostic[]
local function reformat_diagnostics(format, diagnostics)
- vim.validate({
- format = { format, 'f' },
- diagnostics = { diagnostics, 't' },
- })
+ vim.validate('format', format, 'function')
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
local formatted = vim.deepcopy(diagnostics, true)
for _, diagnostic in ipairs(formatted) do
@@ -659,6 +656,28 @@ local function save_extmarks(namespace, bufnr)
api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
end
+--- Create a function that converts a diagnostic severity to an extmark priority.
+--- @param priority integer Base priority
+--- @param opts vim.diagnostic.OptsResolved
+--- @return fun(severity: vim.diagnostic.Severity): integer
+local function severity_to_extmark_priority(priority, opts)
+ if opts.severity_sort then
+ if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
+ return function(severity)
+ return priority + (severity - vim.diagnostic.severity.ERROR)
+ end
+ end
+
+ return function(severity)
+ return priority + (vim.diagnostic.severity.HINT - severity)
+ end
+ end
+
+ return function()
+ return priority
+ end
+end
+
--- @type table<string,true>
local registered_autocmds = {}
@@ -871,14 +890,14 @@ local function next_diagnostic(search_forward, opts)
if opts.win_id then
vim.deprecate('opts.win_id', 'opts.winid', '0.13')
opts.winid = opts.win_id
- opts.win_id = nil
+ opts.win_id = nil --- @diagnostic disable-line
end
-- Support deprecated cursor_position alias
if opts.cursor_position then
vim.deprecate('opts.cursor_position', 'opts.pos', '0.13')
opts.pos = opts.cursor_position
- opts.cursor_position = nil
+ opts.cursor_position = nil --- @diagnostic disable-line
end
local winid = opts.winid or api.nvim_get_current_win()
@@ -959,7 +978,7 @@ local function goto_diagnostic(diagnostic, opts)
if opts.win_id then
vim.deprecate('opts.win_id', 'opts.winid', '0.13')
opts.winid = opts.win_id
- opts.win_id = nil
+ opts.win_id = nil --- @diagnostic disable-line
end
local winid = opts.winid or api.nvim_get_current_win()
@@ -972,8 +991,9 @@ local function goto_diagnostic(diagnostic, opts)
vim.cmd('normal! zv')
end)
- if opts.float then
- local float_opts = type(opts.float) == 'table' and opts.float or {}
+ local float_opts = opts.float
+ if float_opts then
+ float_opts = type(float_opts) == 'table' and float_opts or {}
vim.schedule(function()
M.open_float(vim.tbl_extend('keep', float_opts, {
bufnr = api.nvim_win_get_buf(winid),
@@ -1012,10 +1032,8 @@ end
--- When omitted, update the global diagnostic options.
---@return vim.diagnostic.Opts? : Current diagnostic config if {opts} is omitted.
function M.config(opts, namespace)
- vim.validate({
- opts = { opts, 't', true },
- namespace = { namespace, 'n', true },
- })
+ vim.validate('opts', opts, 'table', true)
+ vim.validate('namespace', namespace, 'number', true)
local t --- @type vim.diagnostic.Opts
if namespace then
@@ -1058,16 +1076,10 @@ end
---@param diagnostics vim.Diagnostic[]
---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
- vim.validate({
- namespace = { namespace, 'n' },
- bufnr = { bufnr, 'n' },
- diagnostics = {
- diagnostics,
- vim.islist,
- 'a list of diagnostics',
- },
- opts = { opts, 't', true },
- })
+ vim.validate('namespace', namespace, 'number')
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
+ vim.validate('opts', opts, 'table', true)
bufnr = get_bufnr(bufnr)
@@ -1092,7 +1104,7 @@ end
---@param namespace integer Diagnostic namespace
---@return vim.diagnostic.NS : Namespace metadata
function M.get_namespace(namespace)
- vim.validate({ namespace = { namespace, 'n' } })
+ vim.validate('namespace', namespace, 'number')
if not all_namespaces[namespace] then
local name --- @type string?
for k, v in pairs(api.nvim_get_namespaces()) do
@@ -1131,10 +1143,8 @@ end
---@return vim.Diagnostic[] : Fields `bufnr`, `end_lnum`, `end_col`, and `severity`
--- are guaranteed to be present.
function M.get(bufnr, opts)
- vim.validate({
- bufnr = { bufnr, 'n', true },
- opts = { opts, 't', true },
- })
+ vim.validate('bufnr', bufnr, 'number', true)
+ vim.validate('opts', opts, 'table', true)
return vim.deepcopy(get_diagnostics(bufnr, opts, false), true)
end
@@ -1147,10 +1157,8 @@ end
---@return table : Table with actually present severity values as keys
--- (see |diagnostic-severity|) and integer counts as values.
function M.count(bufnr, opts)
- vim.validate({
- bufnr = { bufnr, 'n', true },
- opts = { opts, 't', true },
- })
+ vim.validate('bufnr', bufnr, 'number', true)
+ vim.validate('opts', opts, 'table', true)
local diagnostics = get_diagnostics(bufnr, opts, false)
local count = {} --- @type table<integer,integer>
@@ -1309,7 +1317,7 @@ function M.jump(opts)
if opts.cursor_position then
vim.deprecate('opts.cursor_position', 'opts.pos', '0.13')
opts.pos = opts.cursor_position
- opts.cursor_position = nil
+ opts.cursor_position = nil --- @diagnostic disable-line
end
local diag = nil
@@ -1348,16 +1356,10 @@ end
M.handlers.signs = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate({
- namespace = { namespace, 'n' },
- bufnr = { bufnr, 'n' },
- diagnostics = {
- diagnostics,
- vim.islist,
- 'a list of diagnostics',
- },
- opts = { opts, 't', true },
- })
+ vim.validate('namespace', namespace, 'number')
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
+ vim.validate('opts', opts, 'table', true)
bufnr = get_bufnr(bufnr)
opts = opts or {}
@@ -1372,22 +1374,7 @@ M.handlers.signs = {
-- 10 is the default sign priority when none is explicitly specified
local priority = opts.signs and opts.signs.priority or 10
- local get_priority --- @type function
- if opts.severity_sort then
- if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
- get_priority = function(severity)
- return priority + (severity - vim.diagnostic.severity.ERROR)
- end
- else
- get_priority = function(severity)
- return priority + (vim.diagnostic.severity.HINT - severity)
- end
- end
- else
- get_priority = function()
- return priority
- end
- end
+ local get_priority = severity_to_extmark_priority(priority, opts)
local ns = M.get_namespace(namespace)
if not ns.user_data.sign_ns then
@@ -1475,16 +1462,10 @@ M.handlers.signs = {
M.handlers.underline = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate({
- namespace = { namespace, 'n' },
- bufnr = { bufnr, 'n' },
- diagnostics = {
- diagnostics,
- vim.islist,
- 'a list of diagnostics',
- },
- opts = { opts, 't', true },
- })
+ vim.validate('namespace', namespace, 'number')
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
+ vim.validate('opts', opts, 'table', true)
bufnr = get_bufnr(bufnr)
opts = opts or {}
@@ -1504,15 +1485,12 @@ M.handlers.underline = {
end
local underline_ns = ns.user_data.underline_ns
+ local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts)
+
for _, diagnostic in ipairs(diagnostics) do
- --- @type string?
+ -- Default to error if we don't have a highlight associated
local higroup = underline_highlight_map[assert(diagnostic.severity)]
-
- if higroup == nil then
- -- Default to error if we don't have a highlight associated
- -- TODO(lewis6991): this is always nil since underline_highlight_map only has integer keys
- higroup = underline_highlight_map.Error
- end
+ or underline_highlight_map[vim.diagnostic.severity.ERROR]
if diagnostic._tags then
-- TODO(lewis6991): we should be able to stack these.
@@ -1524,13 +1502,13 @@ M.handlers.underline = {
end
end
- vim.highlight.range(
+ vim.hl.range(
bufnr,
underline_ns,
higroup,
{ diagnostic.lnum, diagnostic.col },
{ diagnostic.end_lnum, diagnostic.end_col },
- { priority = vim.highlight.priorities.diagnostics }
+ { priority = get_priority(diagnostic.severity) }
)
end
save_extmarks(underline_ns, bufnr)
@@ -1548,16 +1526,10 @@ M.handlers.underline = {
M.handlers.virtual_text = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate({
- namespace = { namespace, 'n' },
- bufnr = { bufnr, 'n' },
- diagnostics = {
- diagnostics,
- vim.islist,
- 'a list of diagnostics',
- },
- opts = { opts, 't', true },
- })
+ vim.validate('namespace', namespace, 'number')
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
+ vim.validate('opts', opts, 'table', true)
bufnr = get_bufnr(bufnr)
opts = opts or {}
@@ -1681,10 +1653,8 @@ end
---@param bufnr integer? Buffer number, or 0 for current buffer. When
--- omitted, hide diagnostics in all buffers.
function M.hide(namespace, bufnr)
- vim.validate({
- namespace = { namespace, 'n', true },
- bufnr = { bufnr, 'n', true },
- })
+ vim.validate('namespace', namespace, 'number', true)
+ vim.validate('bufnr', bufnr, 'number', true)
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do
@@ -1741,18 +1711,10 @@ end
--- or {bufnr} is nil.
---@param opts? vim.diagnostic.Opts Display options.
function M.show(namespace, bufnr, diagnostics, opts)
- vim.validate({
- namespace = { namespace, 'n', true },
- bufnr = { bufnr, 'n', true },
- diagnostics = {
- diagnostics,
- function(v)
- return v == nil or vim.islist(v)
- end,
- 'a list of diagnostics',
- },
- opts = { opts, 't', true },
- })
+ vim.validate('namespace', namespace, 'number', true)
+ vim.validate('bufnr', bufnr, 'number', true)
+ vim.validate('diagnostics', diagnostics, vim.islist, true, 'a list of diagnostics')
+ vim.validate('opts', opts, 'table', true)
if not bufnr or not namespace then
assert(not diagnostics, 'Cannot show diagnostics without a buffer and namespace')
@@ -1825,9 +1787,7 @@ function M.open_float(opts, ...)
bufnr = opts
opts = ... --- @type vim.diagnostic.Opts.Float
else
- vim.validate({
- opts = { opts, 't', true },
- })
+ vim.validate('opts', opts, 'table', true)
end
opts = opts or {}
@@ -1905,13 +1865,7 @@ function M.open_float(opts, ...)
local highlights = {} --- @type table[]
local header = if_nil(opts.header, 'Diagnostics:')
if header then
- vim.validate({
- header = {
- header,
- { 'string', 'table' },
- "'string' or 'table'",
- },
- })
+ vim.validate('header', header, { 'string', 'table' }, "'string' or 'table'")
if type(header) == 'table' then
-- Don't insert any lines for an empty string
if string.len(if_nil(header[1], '')) > 0 then
@@ -1939,13 +1893,12 @@ function M.open_float(opts, ...)
local prefix, prefix_hl_group --- @type string?, string?
if prefix_opt then
- vim.validate({
- prefix = {
- prefix_opt,
- { 'string', 'table', 'function' },
- "'string' or 'table' or 'function'",
- },
- })
+ vim.validate(
+ 'prefix',
+ prefix_opt,
+ { 'string', 'table', 'function' },
+ "'string' or 'table' or 'function'"
+ )
if type(prefix_opt) == 'string' then
prefix, prefix_hl_group = prefix_opt, 'NormalFloat'
elseif type(prefix_opt) == 'table' then
@@ -1959,13 +1912,12 @@ function M.open_float(opts, ...)
local suffix, suffix_hl_group --- @type string?, string?
if suffix_opt then
- vim.validate({
- suffix = {
- suffix_opt,
- { 'string', 'table', 'function' },
- "'string' or 'table' or 'function'",
- },
- })
+ vim.validate(
+ 'suffix',
+ suffix_opt,
+ { 'string', 'table', 'function' },
+ "'string' or 'table' or 'function'"
+ )
if type(suffix_opt) == 'string' then
suffix, suffix_hl_group = suffix_opt, 'NormalFloat'
elseif type(suffix_opt) == 'table' then
@@ -2038,10 +1990,8 @@ end
---@param bufnr integer? Remove diagnostics for the given buffer. When omitted,
--- diagnostics are removed for all buffers.
function M.reset(namespace, bufnr)
- vim.validate({
- namespace = { namespace, 'n', true },
- bufnr = { bufnr, 'n', true },
- })
+ vim.validate('namespace', namespace, 'number', true)
+ vim.validate('bufnr', bufnr, 'number', true)
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do
@@ -2144,10 +2094,8 @@ function M.enable(enable, filter)
'0.12'
)
- vim.validate({
- enable = { enable, 'n', true }, -- Legacy `bufnr` arg.
- filter = { filter, 'n', true }, -- Legacy `namespace` arg.
- })
+ vim.validate('enable', enable, 'number', true) -- Legacy `bufnr` arg.
+ vim.validate('filter', filter, 'number', true) -- Legacy `namespace` arg.
local ns_id = type(filter) == 'number' and filter or nil
filter = {}
@@ -2156,17 +2104,16 @@ function M.enable(enable, filter)
enable = true
else
filter = filter or {}
- vim.validate({
- enable = { enable, 'b', true },
- filter = { filter, 't', true },
- })
+ vim.validate('enable', enable, 'boolean', true)
+ vim.validate('filter', filter, 'table', true)
end
enable = enable == nil and true or enable
local bufnr = filter.bufnr
+ local ns_id = filter.ns_id
- if bufnr == nil then
- if filter.ns_id == nil then
+ if not bufnr then
+ if not ns_id then
diagnostic_disabled = (
enable
-- Enable everything by setting diagnostic_disabled to an empty table.
@@ -2180,12 +2127,12 @@ function M.enable(enable, filter)
})
)
else
- local ns = M.get_namespace(filter.ns_id)
+ local ns = M.get_namespace(ns_id)
ns.disabled = not enable
end
else
bufnr = get_bufnr(bufnr)
- if filter.ns_id == nil then
+ if not ns_id then
diagnostic_disabled[bufnr] = (not enable) and true or nil
else
if type(diagnostic_disabled[bufnr]) ~= 'table' then
@@ -2195,14 +2142,14 @@ function M.enable(enable, filter)
diagnostic_disabled[bufnr] = {}
end
end
- diagnostic_disabled[bufnr][filter.ns_id] = (not enable) and true or nil
+ diagnostic_disabled[bufnr][ns_id] = (not enable) and true or nil
end
end
if enable then
- M.show(filter.ns_id, bufnr)
+ M.show(ns_id, bufnr)
else
- M.hide(filter.ns_id, bufnr)
+ M.hide(ns_id, bufnr)
end
end
@@ -2234,13 +2181,11 @@ end
--- ERROR.
---@return vim.Diagnostic?: |vim.Diagnostic| structure or `nil` if {pat} fails to match {str}.
function M.match(str, pat, groups, severity_map, defaults)
- vim.validate({
- str = { str, 's' },
- pat = { pat, 's' },
- groups = { groups, 't' },
- severity_map = { severity_map, 't', true },
- defaults = { defaults, 't', true },
- })
+ vim.validate('str', str, 'string')
+ vim.validate('pat', pat, 'string')
+ vim.validate('groups', groups, 'table')
+ vim.validate('severity_map', severity_map, 'table', true)
+ vim.validate('defaults', defaults, 'table', true)
--- @type table<string,vim.diagnostic.Severity>
severity_map = severity_map or M.severity
@@ -2283,13 +2228,7 @@ local errlist_type_map = {
---@param diagnostics vim.Diagnostic[]
---@return table[] : Quickfix list items |setqflist-what|
function M.toqflist(diagnostics)
- vim.validate({
- diagnostics = {
- diagnostics,
- vim.islist,
- 'a list of diagnostics',
- },
- })
+ vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
local list = {} --- @type table[]
for _, v in ipairs(diagnostics) do
@@ -2323,13 +2262,7 @@ end
---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|.
---@return vim.Diagnostic[]
function M.fromqflist(list)
- vim.validate({
- list = {
- list,
- vim.islist,
- 'a list of quickfix items',
- },
- })
+ vim.validate('list', list, 'table')
local diagnostics = {} --- @type vim.Diagnostic[]
for _, item in ipairs(list) do
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index d3910e26eb..e1e73d63fe 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -4,15 +4,22 @@ local fn = vim.fn
local M = {}
--- @alias vim.filetype.mapfn fun(path:string,bufnr:integer, ...):string?, fun(b:integer)?
---- @alias vim.filetype.mapopts { parent: string, priority: number }
+--- @alias vim.filetype.mapopts { priority: number }
--- @alias vim.filetype.maptbl [string|vim.filetype.mapfn, vim.filetype.mapopts]
--- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl
--- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value>
+--- @class vim.filetype.mapping.sorted
+--- @nodoc
+--- @field [1] string parent pattern
+--- @field [2] string pattern
+--- @field [3] string|vim.filetype.mapfn
+--- @field [4] integer priority
+
--- @param ft string|vim.filetype.mapfn
---- @param opts? vim.filetype.mapopts
+--- @param priority? integer
--- @return vim.filetype.maptbl
-local function starsetf(ft, opts)
+local function starsetf(ft, priority)
return {
function(path, bufnr)
-- Note: when `ft` is a function its return value may be nil.
@@ -27,11 +34,8 @@ local function starsetf(ft, opts)
end
end,
{
- -- Allow setting "parent" to be reused in closures, but don't have default as it will be
- -- assigned later from grouping
- parent = opts and opts.parent,
-- Starset matches should have lowest priority by default
- priority = (opts and opts.priority) or -math.huge,
+ priority = priority or -math.huge,
},
}
end
@@ -402,6 +406,7 @@ local extension = {
dtso = 'dts',
its = 'dts',
keymap = 'dts',
+ overlay = 'dts',
dylan = 'dylan',
intr = 'dylanintr',
lid = 'dylanlid',
@@ -587,6 +592,7 @@ local extension = {
ibi = 'ibasic',
icn = 'icon',
idl = detect.idl,
+ idr = 'idris2',
inc = detect.inc,
inf = 'inform',
INF = 'inform',
@@ -594,6 +600,7 @@ local extension = {
inko = 'inko',
inp = detect.inp,
ms = detect_seq(detect.nroff, 'xmath'),
+ ipkg = 'ipkg',
iss = 'iss',
mst = 'ist',
ist = 'ist',
@@ -667,13 +674,14 @@ local extension = {
journal = 'ledger',
ldg = 'ledger',
ledger = 'ledger',
+ leo = 'leo',
less = 'less',
lex = 'lex',
lxx = 'lex',
['l++'] = 'lex',
l = 'lex',
lhs = 'lhaskell',
- ll = 'lifelines',
+ lidr = 'lidris2',
ly = 'lilypond',
ily = 'lilypond',
liquid = 'liquid',
@@ -688,6 +696,7 @@ local extension = {
lt = 'lite',
lite = 'lite',
livemd = 'livebook',
+ ll = detect.ll,
log = detect.log,
Log = detect.log,
LOG = detect.log,
@@ -752,6 +761,7 @@ local extension = {
mib = 'mib',
mix = 'mix',
mixal = 'mix',
+ mlir = 'mlir',
mm = detect.mm,
nb = 'mma',
wl = 'mma',
@@ -782,6 +792,7 @@ local extension = {
mof = 'msidl',
odl = 'msidl',
msql = 'msql',
+ mss = 'mss',
mu = 'mupad',
mush = 'mush',
mustache = 'mustache',
@@ -1067,6 +1078,9 @@ local extension = {
envrc = detect.sh,
ksh = detect.ksh,
sh = detect.sh,
+ lo = 'sh',
+ la = 'sh',
+ lai = 'sh',
mdd = 'sh',
sieve = 'sieve',
siv = 'sieve',
@@ -1146,6 +1160,7 @@ local extension = {
sface = 'surface',
svelte = 'svelte',
svg = 'svg',
+ sw = 'sway',
swift = 'swift',
swiftinterface = 'swift',
swig = 'swig',
@@ -1303,6 +1318,7 @@ local extension = {
xpfm = 'xml',
spfm = 'xml',
bxml = 'xml',
+ mmi = 'xml',
xcu = 'xml',
xlb = 'xml',
xlc = 'xml',
@@ -1610,6 +1626,7 @@ local filename = {
['ldaprc'] = 'ldapconf',
['.ldaprc'] = 'ldapconf',
['ldap.conf'] = 'ldapconf',
+ ['lfrc'] = 'lf',
['lftp.conf'] = 'lftp',
['.lftprc'] = 'lftp',
['/.libao'] = 'libao',
@@ -1862,11 +1879,10 @@ local filename = {
}
-- Re-use closures as much as possible
-local detect_apache_diretc = starsetf('apache', { parent = '/etc/' })
-local detect_apache_dotconf = starsetf('apache', { parent = '%.conf' })
-local detect_muttrc = starsetf('muttrc', { parent = 'utt' })
-local detect_neomuttrc = starsetf('neomuttrc', { parent = 'utt' })
-local detect_xkb = starsetf('xkb', { parent = '/usr/' })
+local detect_apache = starsetf('apache')
+local detect_muttrc = starsetf('muttrc')
+local detect_neomuttrc = starsetf('neomuttrc')
+local detect_xkb = starsetf('xkb')
---@type table<string,vim.filetype.mapping>
local pattern = {
@@ -1883,14 +1899,14 @@ local pattern = {
['/etc/asound%.conf$'] = 'alsaconf',
['/etc/apache2/sites%-.*/.*%.com$'] = 'apache',
['/etc/httpd/.*%.conf$'] = 'apache',
- ['/etc/apache2/.*%.conf'] = detect_apache_diretc,
- ['/etc/apache2/conf%..*/'] = detect_apache_diretc,
- ['/etc/apache2/mods%-.*/'] = detect_apache_diretc,
- ['/etc/apache2/sites%-.*/'] = detect_apache_diretc,
- ['/etc/httpd/conf%..*/'] = detect_apache_diretc,
- ['/etc/httpd/conf%.d/.*%.conf'] = detect_apache_diretc,
- ['/etc/httpd/mods%-.*/'] = detect_apache_diretc,
- ['/etc/httpd/sites%-.*/'] = detect_apache_diretc,
+ ['/etc/apache2/.*%.conf'] = detect_apache,
+ ['/etc/apache2/conf%..*/'] = detect_apache,
+ ['/etc/apache2/mods%-.*/'] = detect_apache,
+ ['/etc/apache2/sites%-.*/'] = detect_apache,
+ ['/etc/httpd/conf%..*/'] = detect_apache,
+ ['/etc/httpd/conf%.d/.*%.conf'] = detect_apache,
+ ['/etc/httpd/mods%-.*/'] = detect_apache,
+ ['/etc/httpd/sites%-.*/'] = detect_apache,
['/etc/proftpd/.*%.conf'] = starsetf('apachestyle'),
['/etc/proftpd/conf%..*/'] = starsetf('apachestyle'),
['/etc/cdrdao%.conf$'] = 'cdrdaoconf',
@@ -2106,6 +2122,7 @@ local pattern = {
['/build/conf/.*%.conf$'] = 'bitbake',
['/meta%-.*/conf/.*%.conf$'] = 'bitbake',
['/meta/conf/.*%.conf$'] = 'bitbake',
+ ['/project%-spec/configs/.*%.conf$'] = 'bitbake',
['/%.cabal/config$'] = 'cabalconfig',
['/cabal/config$'] = 'cabalconfig',
['/%.aws/config$'] = 'confini',
@@ -2132,6 +2149,7 @@ local pattern = {
['/sway/config$'] = 'swayconfig',
['/%.cargo/config$'] = 'toml',
['/%.bundle/config$'] = 'yaml',
+ ['/%.kube/config$'] = 'yaml',
},
['/%.'] = {
['/%.aws/credentials$'] = 'confini',
@@ -2180,11 +2198,13 @@ local pattern = {
},
['%.conf'] = {
['^proftpd%.conf'] = starsetf('apachestyle'),
- ['^access%.conf'] = detect_apache_dotconf,
- ['^apache%.conf'] = detect_apache_dotconf,
- ['^apache2%.conf'] = detect_apache_dotconf,
- ['^httpd%.conf'] = detect_apache_dotconf,
- ['^srm%.conf'] = detect_apache_dotconf,
+ ['^access%.conf'] = detect_apache,
+ ['^apache%.conf'] = detect_apache,
+ ['^apache2%.conf'] = detect_apache,
+ ['^httpd%.conf'] = detect_apache,
+ ['^httpd%-.*%.conf'] = detect_apache,
+ ['^proxy%-html%.conf'] = detect_apache,
+ ['^srm%.conf'] = detect_apache,
['asterisk/.*%.conf'] = starsetf('asterisk'),
['asterisk.*/.*voicemail%.conf'] = starsetf('asteriskvm'),
['^dictd.*%.conf$'] = 'dictdconf',
@@ -2260,6 +2280,7 @@ local pattern = {
['^%.?neomuttrc'] = detect_neomuttrc,
['/%.neomutt/neomuttrc'] = detect_neomuttrc,
['^Neomuttrc'] = detect_neomuttrc,
+ ['%.neomuttdebug'] = 'neomuttlog',
},
['^%.'] = {
['^%.cshrc'] = detect.csh,
@@ -2293,6 +2314,8 @@ local pattern = {
['^crontab%.'] = starsetf('crontab'),
['^cvs%d+$'] = 'cvs',
['^php%.ini%-'] = 'dosini',
+ ['^php%-fpm%.conf'] = 'dosini',
+ ['^www%.conf'] = 'dosini',
['^drac%.'] = starsetf('dracula'),
['/dtrace/.*%.d$'] = 'dtrace',
['esmtprc$'] = 'esmtprc',
@@ -2354,7 +2377,7 @@ local pattern = {
['/app%-defaults/'] = starsetf('xdefaults'),
['^Xresources'] = starsetf('xdefaults'),
-- Increase priority to run before the pattern below
- ['^XF86Config%-4'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }),
+ ['^XF86Config%-4'] = starsetf(detect.xfree86_v4, -math.huge + 1),
['^XF86Config'] = starsetf(detect.xfree86_v3),
['Xmodmap$'] = 'xmodmap',
['xmodmap'] = starsetf('xmodmap'),
@@ -2380,8 +2403,10 @@ local pattern = {
--- @type table<string,vim.filetype.pattern_cache>
local pattern_lookup = {}
+--- @param a vim.filetype.mapping.sorted
+--- @param b vim.filetype.mapping.sorted
local function compare_by_priority(a, b)
- return a[next(a)][2].priority > b[next(b)][2].priority
+ return a[4] > b[4]
end
--- @param pat string
@@ -2391,30 +2416,30 @@ local function parse_pattern(pat)
end
--- @param t table<string,vim.filetype.mapping>
---- @return vim.filetype.mapping[]
---- @return vim.filetype.mapping[]
+--- @return vim.filetype.mapping.sorted[]
+--- @return vim.filetype.mapping.sorted[]
local function sort_by_priority(t)
-- Separate patterns with non-negative and negative priority because they
-- will be processed separately
- local pos = {} --- @type vim.filetype.mapping[]
- local neg = {} --- @type vim.filetype.mapping[]
+ local pos = {} --- @type vim.filetype.mapping.sorted[]
+ local neg = {} --- @type vim.filetype.mapping.sorted[]
for parent, ft_map in pairs(t) do
pattern_lookup[parent] = pattern_lookup[parent] or parse_pattern(parent)
for pat, maptbl in pairs(ft_map) do
- local ft = type(maptbl) == 'table' and maptbl[1] or maptbl
+ local ft_or_fun = type(maptbl) == 'table' and maptbl[1] or maptbl
assert(
- type(ft) == 'string' or type(ft) == 'function',
+ type(ft_or_fun) == 'string' or type(ft_or_fun) == 'function',
'Expected string or function for filetype'
)
-- Parse pattern for common data and cache it once
pattern_lookup[pat] = pattern_lookup[pat] or parse_pattern(pat)
- local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or {}
- opts.parent = opts.parent or parent
- opts.priority = opts.priority or 0
+ --- @type vim.filetype.mapopts?
+ local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or nil
+ local priority = opts and opts.priority or 0
- table.insert(opts.priority >= 0 and pos or neg, { [pat] = { ft, opts } })
+ table.insert(priority >= 0 and pos or neg, { parent, pat, ft_or_fun, priority })
end
end
@@ -2625,7 +2650,8 @@ local function match_pattern(name, path, tail, pat, try_all_candidates)
if some_env_missing then
return nil
end
- pat, has_slash = expanded, expanded:find('/') ~= nil
+ pat = expanded
+ has_slash = has_slash or expanded:find('/') ~= nil
end
-- Try all possible candidates to make parent patterns not depend on slash presence
@@ -2647,14 +2673,13 @@ end
--- @param name string
--- @param path string
--- @param tail string
---- @param pattern_sorted vim.filetype.mapping[]
+--- @param pattern_sorted vim.filetype.mapping.sorted[]
--- @param parent_matches table<string,boolean>
--- @param bufnr integer?
local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_matches, bufnr)
- for i = 1, #pattern_sorted do
- local pat, ft_data = next(pattern_sorted[i])
+ for _, p in ipairs(pattern_sorted) do
+ local parent, pat, ft_or_fn = p[1], p[2], p[3]
- local parent = ft_data[2].parent
local parent_is_matched = parent_matches[parent]
if parent_is_matched == nil then
parent_matches[parent] = match_pattern(name, path, tail, parent, true) ~= nil
@@ -2664,7 +2689,7 @@ local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_mat
if parent_is_matched then
local matches = match_pattern(name, path, tail, pat, false)
if matches then
- local ft, on_detect = dispatch(ft_data[1], path, bufnr, matches)
+ local ft, on_detect = dispatch(ft_or_fn, path, bufnr, matches)
if ft then
return ft, on_detect
end
@@ -2729,9 +2754,7 @@ end
--- filetype specific buffer variables). The function accepts a buffer number as
--- its only argument.
function M.match(args)
- vim.validate({
- arg = { args, 't' },
- })
+ vim.validate('arg', args, 'table')
if not (args.buf or args.filename or args.contents) then
error('At least one of "buf", "filename", or "contents" must be given')
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 1cc81b177f..98b001bd51 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -869,6 +869,16 @@ function M.log(path, _)
end
--- @type vim.filetype.mapfn
+function M.ll(_, bufnr)
+ local first_line = getline(bufnr, 1)
+ if matchregex(first_line, [[;\|\<source_filename\>\|\<target\>]]) then
+ return 'llvm'
+ else
+ return 'lifelines'
+ end
+end
+
+--- @type vim.filetype.mapfn
function M.lpc(_, bufnr)
if vim.g.lpc_syntax_for_c then
for _, line in ipairs(getlines(bufnr, 1, 12)) do
@@ -1908,7 +1918,7 @@ local function match_from_hashbang(contents, path, dispatch_extension)
end
for k, v in pairs(patterns_hashbang) do
- local ft = type(v) == 'table' and v[1] or v
+ local ft = type(v) == 'table' and v[1] or v --[[@as string]]
local opts = type(v) == 'table' and v[2] or {}
if opts.vim_regex and matchregex(name, k) or name:find(k) then
return ft
@@ -2080,6 +2090,7 @@ local function match_from_text(contents, path)
return ft
end
else
+ --- @cast k string
local opts = type(v) == 'table' and v[2] or {}
if opts.start_lnum and opts.end_lnum then
assert(
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index ccddf826f7..d91eeaf02f 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -53,7 +53,7 @@ function M.dirname(file)
if file == nil then
return nil
end
- vim.validate({ file = { file, 's' } })
+ vim.validate('file', file, 'string')
if iswin then
file = file:gsub(os_sep, '/') --[[@as string]]
if file:match('^%w:/?$') then
@@ -83,7 +83,7 @@ function M.basename(file)
if file == nil then
return nil
end
- vim.validate({ file = { file, 's' } })
+ vim.validate('file', file, 'string')
if iswin then
file = file:gsub(os_sep, '/') --[[@as string]]
if file:match('^%w:/?$') then
@@ -123,11 +123,9 @@ end
function M.dir(path, opts)
opts = opts or {}
- vim.validate({
- path = { path, { 'string' } },
- depth = { opts.depth, { 'number' }, true },
- skip = { opts.skip, { 'function' }, true },
- })
+ vim.validate('path', path, 'string')
+ vim.validate('depth', opts.depth, 'number', true)
+ vim.validate('skip', opts.skip, 'function', true)
path = M.normalize(path)
if not opts.depth or opts.depth == 1 then
@@ -231,14 +229,12 @@ end
---@return (string[]) # Normalized paths |vim.fs.normalize()| of all matching items
function M.find(names, opts)
opts = opts or {}
- vim.validate({
- names = { names, { 's', 't', 'f' } },
- path = { opts.path, 's', true },
- upward = { opts.upward, 'b', true },
- stop = { opts.stop, 's', true },
- type = { opts.type, 's', true },
- limit = { opts.limit, 'n', true },
- })
+ vim.validate('names', names, { 'string', 'table', 'function' })
+ vim.validate('path', opts.path, 'string', true)
+ vim.validate('upward', opts.upward, 'boolean', true)
+ vim.validate('stop', opts.stop, 'string', true)
+ vim.validate('type', opts.type, 'string', true)
+ vim.validate('limit', opts.limit, 'number', true)
if type(names) == 'string' then
names = { names }
@@ -547,11 +543,9 @@ function M.normalize(path, opts)
opts = opts or {}
if not opts._fast then
- vim.validate({
- path = { path, { 'string' } },
- expand_env = { opts.expand_env, { 'boolean' }, true },
- win = { opts.win, { 'boolean' }, true },
- })
+ vim.validate('path', path, 'string')
+ vim.validate('expand_env', opts.expand_env, 'boolean', true)
+ vim.validate('win', opts.win, 'boolean', true)
end
local win = opts.win == nil and iswin or not not opts.win
diff --git a/runtime/lua/vim/func/_memoize.lua b/runtime/lua/vim/func/_memoize.lua
index 65210351bf..6e557905a7 100644
--- a/runtime/lua/vim/func/_memoize.lua
+++ b/runtime/lua/vim/func/_memoize.lua
@@ -39,10 +39,8 @@ end
--- @param strong? boolean
--- @return F
return function(hash, fn, strong)
- vim.validate({
- hash = { hash, { 'number', 'string', 'function' } },
- fn = { fn, 'function' },
- })
+ vim.validate('hash', hash, { 'number', 'string', 'function' })
+ vim.validate('fn', fn, 'function')
---@type table<any,table<any,any>>
local cache = {}
diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua
index 22073b15c8..4f86d5e1ca 100644
--- a/runtime/lua/vim/glob.lua
+++ b/runtime/lua/vim/glob.lua
@@ -48,7 +48,7 @@ function M.to_lpeg(pattern)
end
-- luacheck: push ignore s
- local function cut(s, idx, match)
+ local function cut(_s, idx, match)
return idx, match
end
-- luacheck: pop
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/hl.lua
index a8d88db372..099efa3c61 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/hl.lua
@@ -17,7 +17,7 @@ M.priorities = {
user = 200,
}
---- @class vim.highlight.range.Opts
+--- @class vim.hl.range.Opts
--- @inlinedoc
---
--- Type of range. See [getregtype()]
@@ -28,8 +28,8 @@ M.priorities = {
--- (default: `false`)
--- @field inclusive? boolean
---
---- Indicates priority of highlight
---- (default: `vim.highlight.priorities.user`)
+--- Highlight priority
+--- (default: `vim.hl.priorities.user`)
--- @field priority? integer
--- Apply highlight group to range of text.
@@ -39,7 +39,7 @@ M.priorities = {
---@param higroup string Highlight group to use for highlighting
---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()|
---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
----@param opts? vim.highlight.range.Opts
+---@param opts? vim.hl.range.Opts
function M.range(bufnr, ns, higroup, start, finish, opts)
opts = opts or {}
local regtype = opts.regtype or 'v'
@@ -124,7 +124,7 @@ local yank_cancel --- @type fun()?
--- Add the following to your `init.vim`:
---
--- ```vim
---- autocmd TextYankPost * silent! lua vim.highlight.on_yank {higroup='Visual', timeout=300}
+--- autocmd TextYankPost * silent! lua vim.hl.on_yank {higroup='Visual', timeout=300}
--- ```
---
--- @param opts table|nil Optional parameters
@@ -133,21 +133,9 @@ local yank_cancel --- @type fun()?
--- - on_macro highlight when executing macro (default false)
--- - on_visual highlight when yanking visual selection (default true)
--- - event event structure (default vim.v.event)
---- - priority integer priority (default |vim.highlight.priorities|`.user`)
+--- - priority integer priority (default |vim.hl.priorities|`.user`)
function M.on_yank(opts)
- vim.validate({
- opts = {
- opts,
- function(t)
- if t == nil then
- return true
- else
- return type(t) == 'table'
- end
- end,
- 'a table or nil to configure options (see `:h highlight.on_yank`)',
- },
- })
+ vim.validate('opts', opts, 'table', true)
opts = opts or {}
local event = opts.event or vim.v.event
local on_macro = opts.on_macro or false
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
index 50ca0d2d0e..4c19435ef8 100644
--- a/runtime/lua/vim/keymap.lua
+++ b/runtime/lua/vim/keymap.lua
@@ -42,12 +42,10 @@ local keymap = {}
---@see |mapcheck()|
---@see |mapset()|
function keymap.set(mode, lhs, rhs, opts)
- vim.validate({
- mode = { mode, { 's', 't' } },
- lhs = { lhs, 's' },
- rhs = { rhs, { 's', 'f' } },
- opts = { opts, 't', true },
- })
+ vim.validate('mode', mode, { 'string', 'table' })
+ vim.validate('lhs', lhs, 'string')
+ vim.validate('rhs', rhs, { 'string', 'function' })
+ vim.validate('opts', opts, 'table', true)
opts = vim.deepcopy(opts or {}, true)
@@ -107,11 +105,9 @@ end
---@param opts? vim.keymap.del.Opts
---@see |vim.keymap.set()|
function keymap.del(modes, lhs, opts)
- vim.validate({
- mode = { modes, { 's', 't' } },
- lhs = { lhs, 's' },
- opts = { opts, 't', true },
- })
+ vim.validate('mode', modes, { 'string', 'table' })
+ vim.validate('lhs', lhs, 'string')
+ vim.validate('opts', opts, 'table', true)
opts = opts or {}
modes = type(modes) == 'string' and { modes } or modes
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index e86d33bf53..0cce0ab21d 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -4,11 +4,14 @@ local uri_encode = vim.uri_encode --- @type function
--- @type (fun(modename: string): fun()|string)[]
local loaders = package.loaders
+local _loadfile = loadfile
+
+local VERSION = 4
local M = {}
----@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: string}
----@alias CacheEntry {hash:CacheHash, chunk:string}
+--- @alias vim.loader.CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: string}
+--- @alias vim.loader.CacheEntry {hash:vim.loader.CacheHash, chunk:string}
--- @class vim.loader.find.Opts
--- @inlinedoc
@@ -40,107 +43,97 @@ local M = {}
--- @field modname string
---
--- The fs_stat of the module path. Won't be returned for `modname="*"`
---- @field stat? uv.uv_fs_t
+--- @field stat? uv.fs_stat.result
----@alias LoaderStats table<string, {total:number, time:number, [string]:number?}?>
+--- @alias vim.loader.Stats table<string, {total:number, time:number, [string]:number?}?>
----@nodoc
+--- @private
M.path = vim.fn.stdpath('cache') .. '/luac'
----@nodoc
+--- @private
M.enabled = false
----@class (private) Loader
----@field private _rtp string[]
----@field private _rtp_pure string[]
----@field private _rtp_key string
----@field private _hashes? table<string, CacheHash>
-local Loader = {
- VERSION = 4,
- ---@type table<string, table<string,vim.loader.ModuleInfo>>
- _indexed = {},
- ---@type table<string, string[]>
- _topmods = {},
- _loadfile = loadfile,
- ---@type LoaderStats
- _stats = {
- find = { total = 0, time = 0, not_found = 0 },
- },
-}
+--- @type vim.loader.Stats
+local stats = { find = { total = 0, time = 0, not_found = 0 } }
+
+--- @type table<string, uv.fs_stat.result>?
+local fs_stat_cache
+
+--- @type table<string, table<string,vim.loader.ModuleInfo>>
+local indexed = {}
--- @param path string
---- @return CacheHash
---- @private
-function Loader.get_hash(path)
- if not Loader._hashes then
- return uv.fs_stat(path) --[[@as CacheHash]]
+--- @return uv.fs_stat.result?
+local function fs_stat_cached(path)
+ if not fs_stat_cache then
+ return uv.fs_stat(path)
end
- if not Loader._hashes[path] then
+ if not fs_stat_cache[path] then
-- Note we must never save a stat for a non-existent path.
-- For non-existent paths fs_stat() will return nil.
- Loader._hashes[path] = uv.fs_stat(path)
+ fs_stat_cache[path] = uv.fs_stat(path)
end
- return Loader._hashes[path]
+ return fs_stat_cache[path]
end
local function normalize(path)
return fs.normalize(path, { expand_env = false, _fast = true })
end
+local rtp_cached = {} --- @type string[]
+local rtp_cache_key --- @type string?
+
--- Gets the rtp excluding after directories.
--- The result is cached, and will be updated if the runtime path changes.
--- When called from a fast event, the cached value will be returned.
--- @return string[] rtp, boolean updated
----@private
-function Loader.get_rtp()
+local function get_rtp()
if vim.in_fast_event() then
- return (Loader._rtp or {}), false
+ return (rtp_cached or {}), false
end
local updated = false
local key = vim.go.rtp
- if key ~= Loader._rtp_key then
- Loader._rtp = {}
+ if key ~= rtp_cache_key then
+ rtp_cached = {}
for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do
path = normalize(path)
-- skip after directories
if
path:sub(-6, -1) ~= '/after'
- and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path]))
+ and not (indexed[path] and vim.tbl_isempty(indexed[path]))
then
- Loader._rtp[#Loader._rtp + 1] = path
+ rtp_cached[#rtp_cached + 1] = path
end
end
updated = true
- Loader._rtp_key = key
+ rtp_cache_key = key
end
- return Loader._rtp, updated
+ return rtp_cached, updated
end
--- Returns the cache file name
----@param name string can be a module name, or a file name
----@return string file_name
----@private
-function Loader.cache_file(name)
+--- @param name string can be a module name, or a file name
+--- @return string file_name
+local function cache_filename(name)
local ret = ('%s/%s'):format(M.path, uri_encode(name, 'rfc2396'))
return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac')
end
--- Saves the cache entry for a given module or file
----@param name string module name or filename
----@param entry CacheEntry
----@private
-function Loader.write(name, entry)
- local cname = Loader.cache_file(name)
+--- @param cname string cache filename
+--- @param hash vim.loader.CacheHash
+--- @param chunk function
+local function write_cachefile(cname, hash, chunk)
local f = assert(uv.fs_open(cname, 'w', 438))
local header = {
- Loader.VERSION,
- entry.hash.size,
- entry.hash.mtime.sec,
- entry.hash.mtime.nsec,
+ VERSION,
+ hash.size,
+ hash.mtime.sec,
+ hash.mtime.nsec,
}
uv.fs_write(f, table.concat(header, ',') .. '\0')
- uv.fs_write(f, entry.chunk)
+ uv.fs_write(f, string.dump(chunk))
uv.fs_close(f)
end
@@ -150,151 +143,159 @@ end
local function readfile(path, mode)
local f = uv.fs_open(path, 'r', mode)
if f then
- local hash = assert(uv.fs_fstat(f))
- local data = uv.fs_read(f, hash.size, 0) --[[@as string?]]
+ local size = assert(uv.fs_fstat(f)).size
+ local data = uv.fs_read(f, size, 0)
uv.fs_close(f)
return data
end
end
--- Loads the cache entry for a given module or file
----@param name string module name or filename
----@return CacheEntry?
----@private
-function Loader.read(name)
- local cname = Loader.cache_file(name)
+--- @param cname string cache filename
+--- @return vim.loader.CacheHash? hash
+--- @return string? chunk
+local function read_cachefile(cname)
local data = readfile(cname, 438)
- if data then
- local zero = data:find('\0', 1, true)
- if not zero then
- return
- end
+ if not data then
+ return
+ end
- ---@type integer[]|{[0]:integer}
- local header = vim.split(data:sub(1, zero - 1), ',')
- if tonumber(header[1]) ~= Loader.VERSION then
- return
- end
- return {
- hash = {
- size = tonumber(header[2]),
- mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) },
- },
- chunk = data:sub(zero + 1),
- }
+ local zero = data:find('\0', 1, true)
+ if not zero then
+ return
+ end
+
+ --- @type integer[]|{[0]:integer}
+ local header = vim.split(data:sub(1, zero - 1), ',')
+ if tonumber(header[1]) ~= VERSION then
+ return
end
+
+ local hash = {
+ size = tonumber(header[2]),
+ mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) },
+ }
+
+ local chunk = data:sub(zero + 1)
+
+ return hash, chunk
end
--- The `package.loaders` loader for Lua files using the cache.
----@param modname string module name
----@return string|function
----@private
-function Loader.loader(modname)
- Loader._hashes = {}
+--- @param modname string module name
+--- @return string|function
+local function loader_cached(modname)
+ fs_stat_cache = {}
local ret = M.find(modname)[1]
if ret then
-- Make sure to call the global loadfile so we respect any augmentations done elsewhere.
-- E.g. profiling
local chunk, err = loadfile(ret.modpath)
- Loader._hashes = nil
+ fs_stat_cache = nil
return chunk or error(err)
end
- Loader._hashes = nil
+ fs_stat_cache = nil
return ("\n\tcache_loader: module '%s' not found"):format(modname)
end
+local is_win = vim.fn.has('win32') == 1
+
--- The `package.loaders` loader for libs
----@param modname string module name
----@return string|function
----@private
-function Loader.loader_lib(modname)
- local is_win = vim.fn.has('win32') == 1
- local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1]
- if ret then
- -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
- -- a) strip prefix up to and including the first dash, if any
- -- b) replace all dots by underscores
- -- c) prepend "luaopen_"
- -- So "foo-bar.baz" should result in "luaopen_bar_baz"
- local dash = modname:find('-', 1, true)
- local funcname = dash and modname:sub(dash + 1) or modname
- local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_'))
- return chunk or error(err)
+--- @param modname string module name
+--- @return string|function
+local function loader_lib_cached(modname)
+ local ret = M.find(modname, { patterns = { is_win and '.dll' or '.so' } })[1]
+ if not ret then
+ return ("\n\tcache_loader_lib: module '%s' not found"):format(modname)
end
- return ("\n\tcache_loader_lib: module '%s' not found"):format(modname)
-end
---- `loadfile` using the cache
---- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible.
----@param filename? string
----@param _mode? "b"|"t"|"bt"
----@param env? table
----@return function?, string? error_message
----@private
-function Loader.loadfile(filename, _mode, env)
- -- ignore mode, since we byte-compile the Lua source files
- return Loader.load(normalize(filename), { env = env })
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = modname:find('-', 1, true)
+ local funcname = dash and modname:sub(dash + 1) or modname
+ local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_'))
+ return chunk or error(err)
end
--- Checks whether two cache hashes are the same based on:
--- * file size
--- * mtime in seconds
--- * mtime in nanoseconds
----@param h1 CacheHash
----@param h2 CacheHash
----@private
-function Loader.eq(h1, h2)
- return h1
- and h2
- and h1.size == h2.size
- and h1.mtime.sec == h2.mtime.sec
- and h1.mtime.nsec == h2.mtime.nsec
+--- @param a? vim.loader.CacheHash
+--- @param b? vim.loader.CacheHash
+local function hash_eq(a, b)
+ return a
+ and b
+ and a.size == b.size
+ and a.mtime.sec == b.mtime.sec
+ and a.mtime.nsec == b.mtime.nsec
end
---- Loads the given module path using the cache
----@param modpath string
----@param opts? {mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module:
---- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`)
---- - env: (table) the environment to load the module in. (defaults to `nil`)
----@see |luaL_loadfile()|
----@return function?, string? error_message
----@private
-function Loader.load(modpath, opts)
- opts = opts or {}
- local hash = Loader.get_hash(modpath)
- ---@type function?, string?
- local chunk, err
-
- if not hash then
- -- trigger correct error
- return Loader._loadfile(modpath, opts.mode, opts.env)
- end
-
- local entry = Loader.read(modpath)
- if entry and Loader.eq(entry.hash, hash) then
- -- found in cache and up to date
- chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env)
- if not (err and err:find('cannot load incompatible bytecode', 1, true)) then
- return chunk, err
+--- `loadfile` using the cache
+--- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible.
+--- @param filename? string
+--- @param mode? "b"|"t"|"bt"
+--- @param env? table
+--- @return function?, string? error_message
+local function loadfile_cached(filename, mode, env)
+ local modpath = normalize(filename)
+ local stat = fs_stat_cached(modpath)
+ local cname = cache_filename(modpath)
+ if stat then
+ local e_hash, e_chunk = read_cachefile(cname)
+ if hash_eq(e_hash, stat) and e_chunk then
+ -- found in cache and up to date
+ local chunk, err = load(e_chunk, '@' .. modpath, mode, env)
+ if not (err and err:find('cannot load incompatible bytecode', 1, true)) then
+ return chunk, err
+ end
end
end
- entry = { hash = hash, modpath = modpath }
- chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
- if chunk then
- entry.chunk = string.dump(chunk)
- Loader.write(modpath, entry)
+ local chunk, err = _loadfile(modpath, mode, env)
+ if chunk and stat then
+ write_cachefile(cname, stat, chunk)
end
return chunk, err
end
+--- Return the top-level \`/lua/*` modules for this path
+--- @param path string path to check for top-level Lua modules
+local function lsmod(path)
+ if not indexed[path] then
+ indexed[path] = {}
+ for name, t in fs.dir(path .. '/lua') do
+ local modpath = path .. '/lua/' .. name
+ -- HACK: type is not always returned due to a bug in luv
+ t = t or fs_stat_cached(modpath).type
+ --- @type string
+ local topname
+ local ext = name:sub(-4)
+ if ext == '.lua' or ext == '.dll' then
+ topname = name:sub(1, -5)
+ elseif name:sub(-3) == '.so' then
+ topname = name:sub(1, -4)
+ elseif t == 'link' or t == 'directory' then
+ topname = name
+ end
+ if topname then
+ indexed[path][topname] = { modpath = modpath, modname = topname }
+ end
+ end
+ end
+ return indexed[path]
+end
+
--- Finds Lua modules for the given module name.
---
--- @since 0
---
----@param modname string Module name, or `"*"` to find the top-level modules instead
----@param opts? vim.loader.find.Opts Options for finding a module:
----@return vim.loader.ModuleInfo[]
+--- @param modname string Module name, or `"*"` to find the top-level modules instead
+--- @param opts? vim.loader.find.Opts Options for finding a module:
+--- @return vim.loader.ModuleInfo[]
function M.find(modname, opts)
opts = opts or {}
@@ -320,7 +321,7 @@ function M.find(modname, opts)
patterns[p] = '/lua/' .. basename .. pattern
end
- ---@type vim.loader.ModuleInfo[]
+ --- @type vim.loader.ModuleInfo[]
local results = {}
-- Only continue if we haven't found anything yet or we want to find all
@@ -330,23 +331,23 @@ function M.find(modname, opts)
-- Checks if the given paths contain the top-level module.
-- If so, it tries to find the module path for the given module name.
- ---@param paths string[]
+ --- @param paths string[]
local function _find(paths)
for _, path in ipairs(paths) do
if topmod == '*' then
- for _, r in pairs(Loader.lsmod(path)) do
+ for _, r in pairs(lsmod(path)) do
results[#results + 1] = r
if not continue() then
return
end
end
- elseif Loader.lsmod(path)[topmod] then
+ elseif lsmod(path)[topmod] then
for _, pattern in ipairs(patterns) do
local modpath = path .. pattern
- Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1
- local hash = Loader.get_hash(modpath)
- if hash then
- results[#results + 1] = { modpath = modpath, stat = hash, modname = modname }
+ stats.find.stat = (stats.find.stat or 0) + 1
+ local stat = fs_stat_cached(modpath)
+ if stat then
+ results[#results + 1] = { modpath = modpath, stat = stat, modname = modname }
if not continue() then
return
end
@@ -358,9 +359,9 @@ function M.find(modname, opts)
-- always check the rtp first
if opts.rtp ~= false then
- _find(Loader._rtp or {})
+ _find(rtp_cached or {})
if continue() then
- local rtp, updated = Loader.get_rtp()
+ local rtp, updated = get_rtp()
if updated then
_find(rtp)
end
@@ -374,7 +375,7 @@ function M.find(modname, opts)
if #results == 0 then
-- module not found
- Loader._stats.find.not_found = Loader._stats.find.not_found + 1
+ stats.find.not_found = stats.find.not_found + 1
end
return results
@@ -384,17 +385,17 @@ end
---
--- @since 0
---
----@param path string? path to reset
+--- @param path string? path to reset
function M.reset(path)
if path then
- Loader._indexed[normalize(path)] = nil
+ indexed[normalize(path)] = nil
else
- Loader._indexed = {}
+ indexed = {}
end
-- Path could be a directory so just clear all the hashes.
- if Loader._hashes then
- Loader._hashes = {}
+ if fs_stat_cache then
+ fs_stat_cache = {}
end
end
@@ -411,11 +412,11 @@ function M.enable()
end
M.enabled = true
vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p')
- _G.loadfile = Loader.loadfile
+ _G.loadfile = loadfile_cached
-- add Lua loader
- table.insert(loaders, 2, Loader.loader)
+ table.insert(loaders, 2, loader_cached)
-- add libs loader
- table.insert(loaders, 3, Loader.loader_lib)
+ table.insert(loaders, 3, loader_lib_cached)
-- remove Nvim loader
for l, loader in ipairs(loaders) do
if loader == vim._load_package then
@@ -435,111 +436,75 @@ function M.disable()
return
end
M.enabled = false
- _G.loadfile = Loader._loadfile
+ _G.loadfile = _loadfile
for l, loader in ipairs(loaders) do
- if loader == Loader.loader or loader == Loader.loader_lib then
+ if loader == loader_cached or loader == loader_lib_cached then
table.remove(loaders, l)
end
end
table.insert(loaders, 2, vim._load_package)
end
---- Return the top-level \`/lua/*` modules for this path
----@param path string path to check for top-level Lua modules
----@private
-function Loader.lsmod(path)
- if not Loader._indexed[path] then
- Loader._indexed[path] = {}
- for name, t in fs.dir(path .. '/lua') do
- local modpath = path .. '/lua/' .. name
- -- HACK: type is not always returned due to a bug in luv
- t = t or Loader.get_hash(modpath).type
- ---@type string
- local topname
- local ext = name:sub(-4)
- if ext == '.lua' or ext == '.dll' then
- topname = name:sub(1, -5)
- elseif name:sub(-3) == '.so' then
- topname = name:sub(1, -4)
- elseif t == 'link' or t == 'directory' then
- topname = name
- end
- if topname then
- Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
- Loader._topmods[topname] = Loader._topmods[topname] or {}
- if not vim.list_contains(Loader._topmods[topname], path) then
- table.insert(Loader._topmods[topname], path)
- end
- end
- end
- end
- return Loader._indexed[path]
-end
-
--- Tracks the time spent in a function
--- @generic F: function
--- @param f F
--- @return F
---- @private
-function Loader.track(stat, f)
+local function track(stat, f)
return function(...)
local start = vim.uv.hrtime()
local r = { f(...) }
- Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 }
- Loader._stats[stat].total = Loader._stats[stat].total + 1
- Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start
+ stats[stat] = stats[stat] or { total = 0, time = 0 }
+ stats[stat].total = stats[stat].total + 1
+ stats[stat].time = stats[stat].time + uv.hrtime() - start
return unpack(r, 1, table.maxn(r))
end
end
----@class (private) vim.loader._profile.Opts
----@field loaders? boolean Add profiling to the loaders
+--- @class (private) vim.loader._profile.Opts
+--- @field loaders? boolean Add profiling to the loaders
--- Debug function that wraps all loaders and tracks stats
----@private
----@param opts vim.loader._profile.Opts?
+--- Must be called before vim.loader.enable()
+--- @private
+--- @param opts vim.loader._profile.Opts?
function M._profile(opts)
- Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp)
- Loader.read = Loader.track('read', Loader.read)
- Loader.loader = Loader.track('loader', Loader.loader)
- Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib)
- Loader.loadfile = Loader.track('loadfile', Loader.loadfile)
- Loader.load = Loader.track('load', Loader.load)
- M.find = Loader.track('find', M.find)
- Loader.lsmod = Loader.track('lsmod', Loader.lsmod)
+ get_rtp = track('get_rtp', get_rtp)
+ read_cachefile = track('read', read_cachefile)
+ loader_cached = track('loader', loader_cached)
+ loader_lib_cached = track('loader_lib', loader_lib_cached)
+ loadfile_cached = track('loadfile', loadfile_cached)
+ M.find = track('find', M.find)
+ lsmod = track('lsmod', lsmod)
if opts and opts.loaders then
for l, loader in pairs(loaders) do
local loc = debug.getinfo(loader, 'Sn').source:sub(2)
- loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader)
+ loaders[l] = track('loader ' .. l .. ': ' .. loc, loader)
end
end
end
--- Prints all cache stats
----@param opts? {print?:boolean}
----@return LoaderStats
----@private
+--- @param opts? {print?:boolean}
+--- @return vim.loader.Stats
+--- @private
function M._inspect(opts)
if opts and opts.print then
local function ms(nsec)
return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms'
end
- local chunks = {} ---@type string[][]
- ---@type string[]
- local stats = vim.tbl_keys(Loader._stats)
- table.sort(stats)
- for _, stat in ipairs(stats) do
+ local chunks = {} --- @type string[][]
+ for _, stat in vim.spairs(stats) do
vim.list_extend(chunks, {
{ '\n' .. stat .. '\n', 'Title' },
{ '* total: ' },
- { tostring(Loader._stats[stat].total) .. '\n', 'Number' },
+ { tostring(stat.total) .. '\n', 'Number' },
{ '* time: ' },
- { ms(Loader._stats[stat].time) .. '\n', 'Bold' },
+ { ms(stat.time) .. '\n', 'Bold' },
{ '* avg time: ' },
- { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' },
+ { ms(stat.time / stat.total) .. '\n', 'Bold' },
})
- for k, v in pairs(Loader._stats[stat]) do
+ for k, v in pairs(stat) do
if not vim.list_contains({ 'time', 'total' }, k) then
chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) }
chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' }
@@ -548,7 +513,7 @@ function M._inspect(opts)
end
vim.api.nvim_echo(chunks, true, {})
end
- return Loader._stats
+ return stats
end
return M
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 60677554ce..0de3b4ee4d 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -3,7 +3,6 @@ local validate = vim.validate
local lsp = vim._defer_require('vim.lsp', {
_changetracking = ..., --- @module 'vim.lsp._changetracking'
- _dynamic = ..., --- @module 'vim.lsp._dynamic'
_snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
_tagfunc = ..., --- @module 'vim.lsp._tagfunc'
_watchfiles = ..., --- @module 'vim.lsp._watchfiles'
@@ -31,6 +30,13 @@ local changetracking = lsp._changetracking
---@nodoc
lsp.rpc_response_error = lsp.rpc.rpc_response_error
+lsp._resolve_to_request = {
+ [ms.codeAction_resolve] = ms.textDocument_codeAction,
+ [ms.codeLens_resolve] = ms.textDocument_codeLens,
+ [ms.documentLink_resolve] = ms.textDocument_documentLink,
+ [ms.inlayHint_resolve] = ms.textDocument_inlayHint,
+}
+
-- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = {
[ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' },
@@ -86,7 +92,7 @@ lsp._request_name_to_capability = {
---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
---@return integer bufnr
local function resolve_bufnr(bufnr)
- validate({ bufnr = { bufnr, 'n', true } })
+ validate('bufnr', bufnr, 'number', true)
if bufnr == nil or bufnr == 0 then
return api.nvim_get_current_buf()
end
@@ -189,9 +195,10 @@ local function reuse_client_default(client, config)
end
if config.root_dir then
+ local root = vim.uri_from_fname(config.root_dir)
for _, dir in ipairs(client.workspace_folders or {}) do
-- note: do not need to check client.root_dir since that should be client.workspace_folders[1]
- if config.root_dir == dir.name then
+ if root == dir.uri then
return true
end
end
@@ -235,9 +242,9 @@ end
--- - `name` arbitrary name for the LSP client. Should be unique per language server.
--- - `cmd` command string[] or function, described at |vim.lsp.start_client()|.
--- - `root_dir` path to the project root. By default this is used to decide if an existing client
---- should be re-used. The example above uses |vim.fs.root()| and |vim.fs.dirname()| to detect
---- the root by traversing the file system upwards starting from the current directory until
---- either a `pyproject.toml` or `setup.py` file is found.
+--- should be re-used. The example above uses |vim.fs.root()| to detect the root by traversing
+--- the file system upwards starting from the current directory until either a `pyproject.toml`
+--- or `setup.py` file is found.
--- - `workspace_folders` list of `{ uri:string, name: string }` tables specifying the project root
--- folders used by the language server. If `nil` the property is derived from `root_dir` for
--- convenience.
@@ -630,10 +637,8 @@ end
---@param client_id (integer) Client id
---@return boolean success `true` if client was attached successfully; `false` otherwise
function lsp.buf_attach_client(bufnr, client_id)
- validate({
- bufnr = { bufnr, 'n', true },
- client_id = { client_id, 'n' },
- })
+ validate('bufnr', bufnr, 'number', true)
+ validate('client_id', client_id, 'number')
bufnr = resolve_bufnr(bufnr)
if not api.nvim_buf_is_loaded(bufnr) then
log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
@@ -669,10 +674,8 @@ end
---@param bufnr integer Buffer handle, or 0 for current
---@param client_id integer Client id
function lsp.buf_detach_client(bufnr, client_id)
- validate({
- bufnr = { bufnr, 'n', true },
- client_id = { client_id, 'n' },
- })
+ validate('bufnr', bufnr, 'number', true)
+ validate('client_id', client_id, 'number')
bufnr = resolve_bufnr(bufnr)
local client = all_clients[client_id]
@@ -773,7 +776,7 @@ end
---@param filter? vim.lsp.get_clients.Filter
---@return vim.lsp.Client[]: List of |vim.lsp.Client| objects
function lsp.get_clients(filter)
- validate({ filter = { filter, 't', true } })
+ validate('filter', filter, 'table', true)
filter = filter or {}
@@ -858,7 +861,7 @@ api.nvim_create_autocmd('VimLeavePre', {
---
---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
----@param params table|nil Parameters to send to the server
+---@param params? table|(fun(client: vim.lsp.Client, bufnr: integer): table?) Parameters to send to the server
---@param handler? lsp.Handler See |lsp-handler|
--- If nil, follows resolution strategy defined in |lsp-handler-configuration|
---@param on_unsupported? fun()
@@ -870,12 +873,10 @@ api.nvim_create_autocmd('VimLeavePre', {
---cancel all the requests. You could instead
---iterate all clients and call their `cancel_request()` methods.
function lsp.buf_request(bufnr, method, params, handler, on_unsupported)
- validate({
- bufnr = { bufnr, 'n', true },
- method = { method, 's' },
- handler = { handler, 'f', true },
- on_unsupported = { on_unsupported, 'f', true },
- })
+ validate('bufnr', bufnr, 'number', true)
+ validate('method', method, 'string')
+ validate('handler', handler, 'function', true)
+ validate('on_unsupported', on_unsupported, 'function', true)
bufnr = resolve_bufnr(bufnr)
local method_supported = false
@@ -885,7 +886,8 @@ function lsp.buf_request(bufnr, method, params, handler, on_unsupported)
if client.supports_method(method, { bufnr = bufnr }) then
method_supported = true
- local request_success, request_id = client.request(method, params, handler, bufnr)
+ local cparams = type(params) == 'function' and params(client, bufnr) or params --[[@as table?]]
+ local request_success, request_id = client.request(method, cparams, handler, bufnr)
-- This could only fail if the client shut down in the time since we looked
-- it up and we did the request, which should be rare.
if request_success then
@@ -920,35 +922,31 @@ end
---
---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
----@param params (table|nil) Parameters to send to the server
----@param handler fun(results: table<integer, {error: lsp.ResponseError?, result: any}>) (function)
+---@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 handler lsp.MultiHandler (function)
--- Handler called after all requests are completed. Server results are passed as
--- a `client_id:result` map.
---@return function cancel Function that cancels all requests.
function lsp.buf_request_all(bufnr, method, params, handler)
- local results = {} --- @type table<integer,{error: lsp.ResponseError?, result: any}>
- local result_count = 0
- local expected_result_count = 0
-
- local set_expected_result_count = once(function()
- for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
- if client.supports_method(method, { bufnr = bufnr }) then
- expected_result_count = expected_result_count + 1
- end
+ local results = {} --- @type table<integer,{err: lsp.ResponseError?, result: any}>
+ local remaining --- @type integer?
+
+ local _, cancel = lsp.buf_request(bufnr, method, params, function(err, result, ctx, config)
+ if not remaining then
+ -- Calculate as late as possible in case a client is removed during the request
+ remaining = #lsp.get_clients({ bufnr = bufnr, method = method })
end
- end)
- local function _sync_handler(err, result, ctx)
- results[ctx.client_id] = { error = err, result = result }
- result_count = result_count + 1
- set_expected_result_count()
+ -- The error key is deprecated and will be removed in 0.13
+ results[ctx.client_id] = { err = err, error = err, result = result }
+ remaining = remaining - 1
- if result_count >= expected_result_count then
- handler(results)
+ if remaining == 0 then
+ handler(results, ctx, config)
end
- end
-
- local _, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
+ end)
return cancel
end
@@ -992,10 +990,8 @@ end
---
---@return boolean success true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
- validate({
- bufnr = { bufnr, 'n', true },
- method = { method, 's' },
- })
+ validate('bufnr', bufnr, 'number', true)
+ validate('method', method, 'string')
local resp = false
for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
if client.rpc.notify(method, params) then
@@ -1056,7 +1052,7 @@ function lsp.formatexpr(opts)
if client.supports_method(ms.textDocument_rangeFormatting) then
local params = util.make_formatting_params()
local end_line = vim.fn.getline(end_lnum) --[[@as string]]
- local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding)
+ local end_col = vim.str_utfindex(end_line, client.offset_encoding)
--- @cast params +lsp.DocumentRangeFormattingParams
params.range = {
start = {
@@ -1175,6 +1171,7 @@ function lsp.for_each_buffer_client(bufnr, fn)
end
end
+--- @deprecated
--- Function to manage overriding defaults for LSP handlers.
---@param handler (lsp.Handler) See |lsp-handler|
---@param override_config (table) Table containing the keys to override behavior of the {handler}
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
deleted file mode 100644
index 27113c0e74..0000000000
--- a/runtime/lua/vim/lsp/_dynamic.lua
+++ /dev/null
@@ -1,110 +0,0 @@
-local glob = vim.glob
-
---- @class lsp.DynamicCapabilities
---- @field capabilities table<string, lsp.Registration[]>
---- @field client_id number
-local M = {}
-
---- @param client_id number
---- @return lsp.DynamicCapabilities
-function M.new(client_id)
- return setmetatable({
- capabilities = {},
- client_id = client_id,
- }, { __index = M })
-end
-
-function M:supports_registration(method)
- local client = vim.lsp.get_client_by_id(self.client_id)
- if not client then
- return false
- end
- local capability = vim.tbl_get(client.capabilities, unpack(vim.split(method, '/')))
- return type(capability) == 'table' and capability.dynamicRegistration
-end
-
---- @param registrations lsp.Registration[]
-function M:register(registrations)
- -- remove duplicates
- self:unregister(registrations)
- for _, reg in ipairs(registrations) do
- local method = reg.method
- if not self.capabilities[method] then
- self.capabilities[method] = {}
- end
- table.insert(self.capabilities[method], reg)
- end
-end
-
---- @param unregisterations lsp.Unregistration[]
-function M:unregister(unregisterations)
- for _, unreg in ipairs(unregisterations) do
- local method = unreg.method
- if not self.capabilities[method] then
- return
- end
- local id = unreg.id
- for i, reg in ipairs(self.capabilities[method]) do
- if reg.id == id then
- table.remove(self.capabilities[method], i)
- break
- end
- end
- end
-end
-
---- @param method string
---- @param opts? {bufnr: integer?}
---- @return lsp.Registration? (table|nil) the registration if found
-function M:get(method, opts)
- opts = opts or {}
- opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
- for _, reg in ipairs(self.capabilities[method] or {}) do
- if not reg.registerOptions then
- return reg
- end
- local documentSelector = reg.registerOptions.documentSelector
- if not documentSelector then
- return reg
- end
- if self:match(opts.bufnr, documentSelector) then
- return reg
- end
- end
-end
-
---- @param method string
---- @param opts? {bufnr: integer?}
-function M:supports(method, opts)
- return self:get(method, opts) ~= nil
-end
-
---- @param bufnr number
---- @param documentSelector lsp.DocumentSelector
---- @private
-function M:match(bufnr, documentSelector)
- local client = vim.lsp.get_client_by_id(self.client_id)
- if not client then
- return false
- end
- local language = client.get_language_id(bufnr, vim.bo[bufnr].filetype)
- local uri = vim.uri_from_bufnr(bufnr)
- local fname = vim.uri_to_fname(uri)
- for _, filter in ipairs(documentSelector) do
- local matches = true
- if filter.language and language ~= filter.language then
- matches = false
- end
- if matches and filter.scheme and not vim.startswith(uri, filter.scheme .. ':') then
- matches = false
- end
- if matches and filter.pattern and not glob.to_lpeg(filter.pattern):match(fname) then
- matches = false
- end
- if matches then
- return true
- end
- end
-end
-
-return M
diff --git a/runtime/lua/vim/lsp/_meta.lua b/runtime/lua/vim/lsp/_meta.lua
index be3222828d..bf693ccc57 100644
--- a/runtime/lua/vim/lsp/_meta.lua
+++ b/runtime/lua/vim/lsp/_meta.lua
@@ -1,7 +1,8 @@
---@meta
error('Cannot require a meta file')
----@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext, config?: table): ...any
+---@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext): ...any
+---@alias lsp.MultiHandler fun(results: table<integer,{err: lsp.ResponseError?, result: any}>, context: lsp.HandlerContext): ...any
---@class lsp.HandlerContext
---@field method string
diff --git a/runtime/lua/vim/lsp/_tagfunc.lua b/runtime/lua/vim/lsp/_tagfunc.lua
index 4ad50e4a58..f75d43f373 100644
--- a/runtime/lua/vim/lsp/_tagfunc.lua
+++ b/runtime/lua/vim/lsp/_tagfunc.lua
@@ -1,4 +1,5 @@
local lsp = vim.lsp
+local api = vim.api
local util = lsp.util
local ms = lsp.protocol.Methods
@@ -21,32 +22,48 @@ end
---@param pattern string
---@return table[]
local function query_definition(pattern)
- local params = util.make_position_params()
- local results_by_client, err = lsp.buf_request_sync(0, ms.textDocument_definition, params, 1000)
- if err then
+ local bufnr = api.nvim_get_current_buf()
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_definition })
+ if not next(clients) then
return {}
end
+ local win = api.nvim_get_current_win()
local results = {}
+
+ --- @param range lsp.Range
+ --- @param uri string
+ --- @param offset_encoding string
local add = function(range, uri, offset_encoding)
table.insert(results, mk_tag_item(pattern, range, uri, offset_encoding))
end
- for client_id, lsp_results in pairs(assert(results_by_client)) do
- local client = lsp.get_client_by_id(client_id)
- local offset_encoding = client and client.offset_encoding or 'utf-16'
- local result = lsp_results.result or {}
- if result.range then -- Location
- add(result.range, result.uri)
- else
- result = result --[[@as (lsp.Location[]|lsp.LocationLink[])]]
- for _, item in pairs(result) do
- if item.range then -- Location
- add(item.range, item.uri, offset_encoding)
- else -- LocationLink
- add(item.targetSelectionRange, item.targetUri, offset_encoding)
+
+ local remaining = #clients
+ for _, client in ipairs(clients) do
+ ---@param result nil|lsp.Location|lsp.Location[]|lsp.LocationLink[]
+ local function on_response(_, result)
+ if result then
+ local encoding = client.offset_encoding
+ -- single Location
+ if result.range then
+ add(result.range, result.uri, encoding)
+ else
+ for _, location in ipairs(result) do
+ if location.range then -- Location
+ add(location.range, location.uri, encoding)
+ else -- LocationLink
+ add(location.targetSelectionRange, location.targetUri, encoding)
+ end
+ end
end
end
+ remaining = remaining - 1
end
+ local params = util.make_position_params(win, client.offset_encoding)
+ client.request(ms.textDocument_definition, params, on_response, bufnr)
end
+ vim.wait(1000, function()
+ return remaining == 0
+ end)
return results
end
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 98e9818bcd..c4cdb5aea8 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -44,9 +44,8 @@ M._poll_exclude_pattern = glob.to_lpeg('**/.git/{objects,subtree-cache}/**')
--- Registers the workspace/didChangeWatchedFiles capability dynamically.
---
---@param reg lsp.Registration LSP Registration object.
----@param ctx lsp.HandlerContext Context from the |lsp-handler|.
-function M.register(reg, ctx)
- local client_id = ctx.client_id
+---@param client_id integer Client ID.
+function M.register(reg, client_id)
local client = assert(vim.lsp.get_client_by_id(client_id), 'Client must be running')
-- Ill-behaved servers may not honor the client capability and try to register
-- anyway, so ignore requests when the user has opted out of the feature.
@@ -155,9 +154,8 @@ end
--- Unregisters the workspace/didChangeWatchedFiles capability dynamically.
---
---@param unreg lsp.Unregistration LSP Unregistration object.
----@param ctx lsp.HandlerContext Context from the |lsp-handler|.
-function M.unregister(unreg, ctx)
- local client_id = ctx.client_id
+---@param client_id integer Client ID.
+function M.unregister(unreg, client_id)
local client_cancels = cancels[client_id]
local reg_cancels = client_cancels[unreg.id]
while #reg_cancels > 0 do
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 301c1f0cb6..6383855a30 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -1,4 +1,5 @@
local api = vim.api
+local lsp = vim.lsp
local validate = vim.validate
local util = require('vim.lsp.util')
local npcall = vim.F.npcall
@@ -6,28 +7,24 @@ local ms = require('vim.lsp.protocol').Methods
local M = {}
---- Sends an async request to all active clients attached to the current
---- buffer.
----
----@param method (string) LSP method name
----@param params (table|nil) Parameters to send to the server
----@param handler lsp.Handler? See |lsp-handler|. Follows |lsp-handler-resolution|
----
----@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
----for all successful requests.
----@return function _cancel_all_requests Function which can be used to
----cancel all the requests. You could instead
----iterate all clients and call their `cancel_request()` methods.
----
----@see |vim.lsp.buf_request()|
-local function request(method, params, handler)
- validate({
- method = { method, 's' },
- handler = { handler, 'f', true },
- })
- return vim.lsp.buf_request(0, method, params, handler)
+--- @param params? table
+--- @return fun(client: vim.lsp.Client): lsp.TextDocumentPositionParams
+local function client_positional_params(params)
+ local win = api.nvim_get_current_win()
+ return function(client)
+ local ret = util.make_position_params(win, client.offset_encoding)
+ if params then
+ ret = vim.tbl_extend('force', ret, params)
+ end
+ return ret
+ end
end
+local hover_ns = api.nvim_create_namespace('vim_lsp_hover_range')
+
+--- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts
+--- @field silent? boolean
+
--- Displays hover information about the symbol under the cursor in a floating
--- window. The window will be dismissed on cursor move.
--- Calling the function twice will jump into the floating window
@@ -35,21 +32,210 @@ end
--- In the floating window, all commands and mappings are available as usual,
--- except that "q" dismisses the window.
--- You can scroll the contents the same as you would any other buffer.
-function M.hover()
- local params = util.make_position_params()
- request(ms.textDocument_hover, params)
+---
+--- Note: to disable hover highlights, add the following to your config:
+---
+--- ```lua
+--- vim.api.nvim_create_autocmd('ColorScheme', {
+--- callback = function()
+--- vim.api.nvim_set_hl(0, 'LspReferenceTarget', {})
+--- end,
+--- })
+--- ```
+--- @param config? vim.lsp.buf.hover.Opts
+function M.hover(config)
+ config = config or {}
+ config.focus_id = ms.textDocument_hover
+
+ lsp.buf_request_all(0, ms.textDocument_hover, client_positional_params(), function(results, ctx)
+ local bufnr = assert(ctx.bufnr)
+ if api.nvim_get_current_buf() ~= bufnr then
+ -- Ignore result since buffer changed. This happens for slow language servers.
+ return
+ end
+
+ -- Filter errors from results
+ local results1 = {} --- @type table<integer,lsp.Hover>
+
+ for client_id, resp in pairs(results) do
+ local err, result = resp.err, resp.result
+ if err then
+ lsp.log.error(err.code, err.message)
+ elseif result then
+ results1[client_id] = result
+ end
+ end
+
+ if vim.tbl_isempty(results1) then
+ if config.silent ~= true then
+ vim.notify('No information available')
+ end
+ return
+ end
+
+ local contents = {} --- @type string[]
+
+ local nresults = #vim.tbl_keys(results1)
+
+ local format = 'markdown'
+
+ for client_id, result in pairs(results1) do
+ local client = assert(lsp.get_client_by_id(client_id))
+ if nresults > 1 then
+ -- Show client name if there are multiple clients
+ contents[#contents + 1] = string.format('# %s', client.name)
+ end
+ if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
+ if #results1 == 1 then
+ format = 'plaintext'
+ contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
+ else
+ -- Surround plaintext with ``` to get correct formatting
+ contents[#contents + 1] = '```'
+ vim.list_extend(
+ contents,
+ vim.split(result.contents.value or '', '\n', { trimempty = true })
+ )
+ contents[#contents + 1] = '```'
+ end
+ else
+ vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))
+ end
+ local range = result.range
+ if range then
+ local start = range.start
+ local end_ = range['end']
+ local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)
+ local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)
+
+ vim.hl.range(
+ bufnr,
+ hover_ns,
+ 'LspReferenceTarget',
+ { start.line, start_idx },
+ { end_.line, end_idx },
+ { priority = vim.hl.priorities.user }
+ )
+ end
+ contents[#contents + 1] = '---'
+ end
+
+ -- Remove last linebreak ('---')
+ contents[#contents] = nil
+
+ if vim.tbl_isempty(contents) then
+ if config.silent ~= true then
+ vim.notify('No information available')
+ end
+ return
+ end
+
+ local _, winid = lsp.util.open_floating_preview(contents, format, config)
+
+ api.nvim_create_autocmd('WinClosed', {
+ pattern = tostring(winid),
+ once = true,
+ callback = function()
+ api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1)
+ return true
+ end,
+ })
+ end)
end
local function request_with_opts(name, params, opts)
local req_handler --- @type function?
if opts then
req_handler = function(err, result, ctx, config)
- local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
- local handler = client.handlers[name] or vim.lsp.handlers[name]
+ local client = assert(lsp.get_client_by_id(ctx.client_id))
+ local handler = client.handlers[name] or lsp.handlers[name]
handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts))
end
end
- request(name, params, req_handler)
+ lsp.buf_request(0, name, params, req_handler)
+end
+
+---@param method string
+---@param opts? vim.lsp.LocationOpts
+local function get_locations(method, opts)
+ opts = opts or {}
+ local bufnr = api.nvim_get_current_buf()
+ local clients = lsp.get_clients({ method = method, bufnr = bufnr })
+ if not next(clients) then
+ vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
+ return
+ end
+ local win = api.nvim_get_current_win()
+ local from = vim.fn.getpos('.')
+ from[1] = bufnr
+ local tagname = vim.fn.expand('<cword>')
+ local remaining = #clients
+
+ ---@type vim.quickfix.entry[]
+ local all_items = {}
+
+ ---@param result nil|lsp.Location|lsp.Location[]
+ ---@param client vim.lsp.Client
+ local function on_response(_, result, client)
+ local locations = {}
+ if result then
+ locations = vim.islist(result) and result or { result }
+ end
+ local items = util.locations_to_items(locations, client.offset_encoding)
+ vim.list_extend(all_items, items)
+ remaining = remaining - 1
+ if remaining == 0 then
+ if vim.tbl_isempty(all_items) then
+ vim.notify('No locations found', vim.log.levels.INFO)
+ return
+ end
+
+ local title = 'LSP locations'
+ if opts.on_list then
+ assert(vim.is_callable(opts.on_list), 'on_list is not a function')
+ opts.on_list({
+ title = title,
+ items = all_items,
+ context = { bufnr = bufnr, method = method },
+ })
+ return
+ end
+
+ if #all_items == 1 then
+ local item = all_items[1]
+ local b = item.bufnr or vim.fn.bufadd(item.filename)
+
+ -- Save position in jumplist
+ vim.cmd("normal! m'")
+ -- Push a new item into tagstack
+ local tagstack = { { tagname = tagname, from = from } }
+ vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
+
+ vim.bo[b].buflisted = true
+ local w = opts.reuse_win and vim.fn.win_findbuf(b)[1] or win
+ api.nvim_win_set_buf(w, b)
+ api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 })
+ vim._with({ win = w }, function()
+ -- Open folds under the cursor
+ vim.cmd('normal! zv')
+ end)
+ return
+ end
+ if opts.loclist then
+ vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items })
+ vim.cmd.lopen()
+ else
+ vim.fn.setqflist({}, ' ', { title = title, items = all_items })
+ vim.cmd('botright copen')
+ end
+ end
+ end
+ for _, client in ipairs(clients) do
+ local params = util.make_position_params(win, client.offset_encoding)
+ client.request(method, params, function(_, result)
+ on_response(_, result, client)
+ end)
+ end
end
--- @class vim.lsp.ListOpts
@@ -89,39 +275,145 @@ end
--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
--- @param opts? vim.lsp.LocationOpts
function M.declaration(opts)
- local params = util.make_position_params()
- request_with_opts(ms.textDocument_declaration, params, opts)
+ get_locations(ms.textDocument_declaration, opts)
end
--- Jumps to the definition of the symbol under the cursor.
--- @param opts? vim.lsp.LocationOpts
function M.definition(opts)
- local params = util.make_position_params()
- request_with_opts(ms.textDocument_definition, params, opts)
+ get_locations(ms.textDocument_definition, opts)
end
--- Jumps to the definition of the type of the symbol under the cursor.
--- @param opts? vim.lsp.LocationOpts
function M.type_definition(opts)
- local params = util.make_position_params()
- request_with_opts(ms.textDocument_typeDefinition, params, opts)
+ get_locations(ms.textDocument_typeDefinition, opts)
end
--- Lists all the implementations for the symbol under the cursor in the
--- quickfix window.
--- @param opts? vim.lsp.LocationOpts
function M.implementation(opts)
- local params = util.make_position_params()
- request_with_opts(ms.textDocument_implementation, params, opts)
+ get_locations(ms.textDocument_implementation, opts)
end
+--- @param results table<integer,{err: lsp.ResponseError?, result: lsp.SignatureHelp?}>
+local function process_signature_help_results(results)
+ local signatures = {} --- @type [vim.lsp.Client,lsp.SignatureInformation][]
+
+ -- Pre-process results
+ for client_id, r in pairs(results) do
+ local err = r.err
+ local client = assert(lsp.get_client_by_id(client_id))
+ if err then
+ vim.notify(
+ client.name .. ': ' .. tostring(err.code) .. ': ' .. err.message,
+ vim.log.levels.ERROR
+ )
+ api.nvim_command('redraw')
+ else
+ local result = r.result --- @type lsp.SignatureHelp
+ if result and result.signatures and result.signatures[1] then
+ for _, sig in ipairs(result.signatures) do
+ signatures[#signatures + 1] = { client, sig }
+ end
+ end
+ end
+ end
+
+ return signatures
+end
+
+local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
+
+--- @class vim.lsp.buf.signature_help.Opts : vim.lsp.util.open_floating_preview.Opts
+--- @field silent? boolean
+
+-- TODO(lewis6991): support multiple clients
--- Displays signature information about the symbol under the cursor in a
--- floating window.
-function M.signature_help()
- local params = util.make_position_params()
- request(ms.textDocument_signatureHelp, params)
+--- @param config? vim.lsp.buf.signature_help.Opts
+function M.signature_help(config)
+ local method = ms.textDocument_signatureHelp
+
+ config = config and vim.deepcopy(config) or {}
+ config.focus_id = method
+
+ lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx)
+ if api.nvim_get_current_buf() ~= ctx.bufnr then
+ -- Ignore result since buffer changed. This happens for slow language servers.
+ return
+ end
+
+ local signatures = process_signature_help_results(results)
+
+ if not next(signatures) then
+ if config.silent ~= true then
+ print('No signature help available')
+ end
+ return
+ end
+
+ local ft = vim.bo[ctx.bufnr].filetype
+ local total = #signatures
+ local idx = 0
+
+ --- @param update_win? integer
+ local function show_signature(update_win)
+ idx = (idx % total) + 1
+ local client, result = signatures[idx][1], signatures[idx][2]
+ --- @type string[]?
+ local triggers =
+ vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
+ local lines, hl =
+ util.convert_signature_help_to_markdown_lines({ signatures = { result } }, ft, triggers)
+ if not lines then
+ return
+ end
+
+ local sfx = total > 1 and string.format(' (%d/%d) (<C-s> to cycle)', idx, total) or ''
+ local title = string.format('Signature Help: %s%s', client.name, sfx)
+ if config.border then
+ config.title = title
+ else
+ table.insert(lines, 1, '# ' .. title)
+ if hl then
+ hl[1] = hl[1] + 1
+ hl[3] = hl[3] + 1
+ end
+ end
+
+ config._update_win = update_win
+
+ local buf, win = util.open_floating_preview(lines, 'markdown', config)
+
+ if hl then
+ vim.api.nvim_buf_clear_namespace(buf, sig_help_ns, 0, -1)
+ vim.hl.range(
+ buf,
+ sig_help_ns,
+ 'LspSignatureActiveParameter',
+ { hl[1], hl[2] },
+ { hl[3], hl[4] }
+ )
+ end
+ return buf, win
+ end
+
+ local fbuf, fwin = show_signature()
+
+ if total > 1 then
+ vim.keymap.set('n', '<C-s>', function()
+ show_signature(fwin)
+ end, {
+ buffer = fbuf,
+ desc = 'Cycle next signature',
+ })
+ end
+ end)
end
+--- @deprecated
--- Retrieves the completion items at the current cursor position. Can only be
--- called in Insert mode.
---
@@ -131,9 +423,14 @@ end
---
---@see vim.lsp.protocol.CompletionTriggerKind
function M.completion(context)
- local params = util.make_position_params()
- params.context = context
- return request(ms.textDocument_completion, params)
+ vim.depends('vim.lsp.buf.completion', 'vim.lsp.commpletion.trigger', '0.12')
+ return lsp.buf_request(
+ 0,
+ ms.textDocument_completion,
+ client_positional_params({
+ context = context,
+ })
+ )
end
---@param bufnr integer
@@ -240,7 +537,7 @@ function M.format(opts)
method = ms.textDocument_formatting
end
- local clients = vim.lsp.get_clients({
+ local clients = lsp.get_clients({
id = opts.id,
bufnr = bufnr,
name = opts.name,
@@ -277,7 +574,7 @@ function M.format(opts)
end
local params = set_range(client, util.make_formatting_params(opts.formatting_options))
client.request(method, params, function(...)
- local handler = client.handlers[method] or vim.lsp.handlers[method]
+ local handler = client.handlers[method] or lsp.handlers[method]
handler(...)
do_format(next(clients, idx))
end, bufnr)
@@ -319,7 +616,7 @@ end
function M.rename(new_name, opts)
opts = opts or {}
local bufnr = opts.bufnr or api.nvim_get_current_buf()
- local clients = vim.lsp.get_clients({
+ local clients = lsp.get_clients({
bufnr = bufnr,
name = opts.name,
-- Clients must at least support rename, prepareRename is optional
@@ -338,6 +635,8 @@ function M.rename(new_name, opts)
-- Compute early to account for cursor movements after going async
local cword = vim.fn.expand('<cword>')
+ --- @param range lsp.Range
+ --- @param offset_encoding string
local function get_text_at_range(range, offset_encoding)
return api.nvim_buf_get_text(
bufnr,
@@ -359,7 +658,7 @@ function M.rename(new_name, opts)
local params = util.make_position_params(win, client.offset_encoding)
params.newName = name
local handler = client.handlers[ms.textDocument_rename]
- or vim.lsp.handlers[ms.textDocument_rename]
+ or lsp.handlers[ms.textDocument_rename]
client.request(ms.textDocument_rename, params, function(...)
handler(...)
try_use_client(next(clients, idx))
@@ -437,12 +736,60 @@ end
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
---@param opts? vim.lsp.ListOpts
function M.references(context, opts)
- validate({ context = { context, 't', true } })
- local params = util.make_position_params()
- params.context = context or {
- includeDeclaration = true,
- }
- request_with_opts(ms.textDocument_references, params, opts)
+ validate('context', context, 'table', true)
+ local bufnr = api.nvim_get_current_buf()
+ local clients = lsp.get_clients({ method = ms.textDocument_references, bufnr = bufnr })
+ if not next(clients) then
+ return
+ end
+ local win = api.nvim_get_current_win()
+ opts = opts or {}
+
+ local all_items = {}
+ local title = 'References'
+
+ local function on_done()
+ if not next(all_items) then
+ vim.notify('No references found')
+ else
+ local list = {
+ title = title,
+ items = all_items,
+ context = {
+ method = ms.textDocument_references,
+ bufnr = bufnr,
+ },
+ }
+ if opts.loclist then
+ vim.fn.setloclist(0, {}, ' ', list)
+ vim.cmd.lopen()
+ elseif opts.on_list then
+ assert(vim.is_callable(opts.on_list), 'on_list is not a function')
+ opts.on_list(list)
+ else
+ vim.fn.setqflist({}, ' ', list)
+ vim.cmd('botright copen')
+ end
+ end
+ end
+
+ local remaining = #clients
+ for _, client in ipairs(clients) do
+ local params = util.make_position_params(win, client.offset_encoding)
+
+ ---@diagnostic disable-next-line: inject-field
+ params.context = context or {
+ includeDeclaration = true,
+ }
+ client.request(ms.textDocument_references, params, function(_, result)
+ local items = util.locations_to_items(result or {}, client.offset_encoding)
+ vim.list_extend(all_items, items)
+ remaining = remaining - 1
+ if remaining == 0 then
+ on_done()
+ end
+ end)
+ end
end
--- Lists all symbols in the current buffer in the quickfix window.
@@ -452,65 +799,116 @@ function M.document_symbol(opts)
request_with_opts(ms.textDocument_documentSymbol, params, opts)
end
---- @param call_hierarchy_items lsp.CallHierarchyItem[]
---- @return lsp.CallHierarchyItem?
-local function pick_call_hierarchy_item(call_hierarchy_items)
- if #call_hierarchy_items == 1 then
- return call_hierarchy_items[1]
- end
- local items = {}
- for i, item in pairs(call_hierarchy_items) do
- local entry = item.detail or item.name
- table.insert(items, string.format('%d. %s', i, entry))
- end
- local choice = vim.fn.inputlist(items)
- if choice < 1 or choice > #items then
+--- @param client_id integer
+--- @param method string
+--- @param params table
+--- @param handler? lsp.Handler
+--- @param bufnr? integer
+local function request_with_id(client_id, method, params, handler, bufnr)
+ local client = lsp.get_client_by_id(client_id)
+ if not client then
+ vim.notify(
+ string.format('Client with id=%d disappeared during hierarchy request', client_id),
+ vim.log.levels.WARN
+ )
return
end
- return call_hierarchy_items[choice]
+ client.request(method, params, handler, bufnr)
+end
+
+--- @param item lsp.TypeHierarchyItem|lsp.CallHierarchyItem
+local function format_hierarchy_item(item)
+ if not item.detail or #item.detail == 0 then
+ return item.name
+ end
+ return string.format('%s %s', item.name, item.detail)
end
+local hierarchy_methods = {
+ [ms.typeHierarchy_subtypes] = 'type',
+ [ms.typeHierarchy_supertypes] = 'type',
+ [ms.callHierarchy_incomingCalls] = 'call',
+ [ms.callHierarchy_outgoingCalls] = 'call',
+}
+
--- @param method string
-local function call_hierarchy(method)
- local params = util.make_position_params()
- --- @param result lsp.CallHierarchyItem[]?
- request(ms.textDocument_prepareCallHierarchy, params, function(err, result, ctx)
- if err then
- vim.notify(err.message, vim.log.levels.WARN)
- return
- end
- if not result or vim.tbl_isempty(result) then
+local function hierarchy(method)
+ local kind = hierarchy_methods[method]
+ if not kind then
+ error('unsupported method ' .. method)
+ end
+
+ local prepare_method = kind == 'type' and ms.textDocument_prepareTypeHierarchy
+ or ms.textDocument_prepareCallHierarchy
+
+ local bufnr = api.nvim_get_current_buf()
+ local clients = lsp.get_clients({ bufnr = bufnr, method = prepare_method })
+ if not next(clients) then
+ vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
+ return
+ end
+
+ local win = api.nvim_get_current_win()
+
+ --- @param results [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
+ local function on_response(results)
+ if #results == 0 then
vim.notify('No item resolved', vim.log.levels.WARN)
- return
- end
- local call_hierarchy_item = pick_call_hierarchy_item(result)
- if not call_hierarchy_item then
- return
- end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
- if client then
- client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
+ elseif #results == 1 then
+ local client_id, item = results[1][1], results[1][2]
+ request_with_id(client_id, method, { item = item }, nil, bufnr)
else
- vim.notify(
- string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
- vim.log.levels.WARN
- )
+ vim.ui.select(results, {
+ prompt = string.format('Select a %s hierarchy item:', kind),
+ kind = kind .. 'hierarchy',
+ format_item = function(x)
+ return format_hierarchy_item(x[2])
+ end,
+ }, function(x)
+ if x then
+ local client_id, item = x[1], x[2]
+ request_with_id(client_id, method, { item = item }, nil, bufnr)
+ end
+ end)
end
- end)
+ end
+
+ local results = {} --- @type [integer, lsp.TypeHierarchyItem|lsp.CallHierarchyItem][]
+
+ local remaining = #clients
+
+ for _, client in ipairs(clients) do
+ local params = util.make_position_params(win, client.offset_encoding)
+ --- @param result lsp.CallHierarchyItem[]|lsp.TypeHierarchyItem[]?
+ client.request(prepare_method, params, function(err, result, ctx)
+ if err then
+ vim.notify(err.message, vim.log.levels.WARN)
+ elseif result then
+ for _, item in ipairs(result) do
+ results[#results + 1] = { ctx.client_id, item }
+ end
+ end
+
+ remaining = remaining - 1
+ if remaining == 0 then
+ on_response(results)
+ end
+ end, bufnr)
+ end
end
--- Lists all the call sites of the symbol under the cursor in the
--- |quickfix| window. If the symbol can resolve to multiple
--- items, the user can pick one in the |inputlist()|.
function M.incoming_calls()
- call_hierarchy(ms.callHierarchy_incomingCalls)
+ hierarchy(ms.callHierarchy_incomingCalls)
end
--- Lists all the items that are called by the symbol under the
--- cursor in the |quickfix| window. If the symbol can resolve to
--- multiple items, the user can pick one in the |inputlist()|.
function M.outgoing_calls()
- call_hierarchy(ms.callHierarchy_outgoingCalls)
+ hierarchy(ms.callHierarchy_outgoingCalls)
end
--- Lists all the subtypes or supertypes of the symbol under the
@@ -519,79 +917,14 @@ end
---@param kind "subtypes"|"supertypes"
function M.typehierarchy(kind)
local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes
-
- --- Merge results from multiple clients into a single table. Client-ID is preserved.
- ---
- --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
- --- @return [integer, lsp.TypeHierarchyItem][]
- local function merge_results(results)
- local merged_results = {}
- for client_id, client_result in pairs(results) do
- if client_result.error then
- vim.notify(client_result.error.message, vim.log.levels.WARN)
- elseif client_result.result then
- for _, item in pairs(client_result.result) do
- table.insert(merged_results, { client_id, item })
- end
- end
- end
- return merged_results
- end
-
- local bufnr = api.nvim_get_current_buf()
- local params = util.make_position_params()
- --- @param results table<integer, {error: lsp.ResponseError?, result: lsp.TypeHierarchyItem[]?}>
- vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results)
- local merged_results = merge_results(results)
- if #merged_results == 0 then
- vim.notify('No items resolved', vim.log.levels.INFO)
- return
- end
-
- if #merged_results == 1 then
- local item = merged_results[1]
- local client = vim.lsp.get_client_by_id(item[1])
- if client then
- client.request(method, { item = item[2] }, nil, bufnr)
- else
- vim.notify(
- string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
- vim.log.levels.WARN
- )
- end
- else
- local select_opts = {
- prompt = 'Select a type hierarchy item:',
- kind = 'typehierarchy',
- format_item = function(item)
- if not item[2].detail or #item[2].detail == 0 then
- return item[2].name
- end
- return string.format('%s %s', item[2].name, item[2].detail)
- end,
- }
-
- vim.ui.select(merged_results, select_opts, function(item)
- local client = vim.lsp.get_client_by_id(item[1])
- if client then
- --- @type lsp.TypeHierarchyItem
- client.request(method, { item = item[2] }, nil, bufnr)
- else
- vim.notify(
- string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
- vim.log.levels.WARN
- )
- end
- end)
- end
- end)
+ hierarchy(method)
end
--- List workspace folders.
---
function M.list_workspace_folders()
local workspace_folders = {}
- for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do
+ for _, client in pairs(lsp.get_clients({ bufnr = 0 })) do
for _, folder in pairs(client.workspace_folders or {}) do
table.insert(workspace_folders, folder.name)
end
@@ -614,7 +947,7 @@ function M.add_workspace_folder(workspace_folder)
return
end
local bufnr = api.nvim_get_current_buf()
- for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
client:_add_workspace_folder(workspace_folder)
end
end
@@ -631,7 +964,7 @@ function M.remove_workspace_folder(workspace_folder)
return
end
local bufnr = api.nvim_get_current_buf()
- for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
client:_remove_workspace_folder(workspace_folder)
end
print(workspace_folder, 'is not currently part of the workspace')
@@ -670,8 +1003,7 @@ end
--- |hl-LspReferenceRead|
--- |hl-LspReferenceWrite|
function M.document_highlight()
- local params = util.make_position_params()
- request(ms.textDocument_documentHighlight, params)
+ lsp.buf_request(0, ms.textDocument_documentHighlight, client_positional_params())
end
--- Removes document highlights from current buffer.
@@ -773,7 +1105,8 @@ local function on_code_action_results(results, opts)
local a_cmd = action.command
if a_cmd then
local command = type(a_cmd) == 'table' and a_cmd or action
- client:_exec_cmd(command, ctx)
+ --- @cast command lsp.Command
+ client:exec_cmd(command, ctx)
end
end
@@ -794,16 +1127,11 @@ local function on_code_action_results(results, opts)
-- command: string
-- arguments?: any[]
--
- local client = assert(vim.lsp.get_client_by_id(choice.ctx.client_id))
+ local client = assert(lsp.get_client_by_id(choice.ctx.client_id))
local action = choice.action
local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
- local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = bufnr })
-
- local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider')
- or client.supports_method(ms.codeAction_resolve)
-
- if not action.edit and client and supports_resolve then
+ if not action.edit and client.supports_method(ms.codeAction_resolve) then
client.request(ms.codeAction_resolve, action, function(err, resolved_action)
if err then
if action.command then
@@ -827,11 +1155,19 @@ local function on_code_action_results(results, opts)
return
end
- ---@param item {action: lsp.Command|lsp.CodeAction}
+ ---@param item {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
local function format_item(item)
- local title = item.action.title:gsub('\r\n', '\\r\\n')
- return title:gsub('\n', '\\n')
+ local clients = lsp.get_clients({ bufnr = item.ctx.bufnr })
+ local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n')
+
+ if #clients == 1 then
+ return title
+ end
+
+ local source = lsp.get_client_by_id(item.ctx.client_id).name
+ return ('%s [%s]'):format(title, source)
end
+
local select_opts = {
prompt = 'Code actions:',
kind = 'codeaction',
@@ -847,7 +1183,7 @@ end
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
---@see vim.lsp.protocol.CodeActionTriggerKind
function M.code_action(opts)
- validate({ options = { opts, 't', true } })
+ validate('options', opts, 'table', true)
opts = opts or {}
-- Detect old API call code_action(context) which should now be
-- code_action({ context = context} )
@@ -857,16 +1193,16 @@ function M.code_action(opts)
end
local context = opts.context and vim.deepcopy(opts.context) or {}
if not context.triggerKind then
- context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked
+ context.triggerKind = lsp.protocol.CodeActionTriggerKind.Invoked
end
local mode = api.nvim_get_mode().mode
local bufnr = api.nvim_get_current_buf()
local win = api.nvim_get_current_win()
- local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
+ local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
local remaining = #clients
if remaining == 0 then
- if next(vim.lsp.get_clients({ bufnr = bufnr })) then
- vim.notify(vim.lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
+ if next(lsp.get_clients({ bufnr = bufnr })) then
+ vim.notify(lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
end
return
end
@@ -903,8 +1239,8 @@ function M.code_action(opts)
if context.diagnostics then
params.context = context
else
- local ns_push = vim.lsp.diagnostic.get_namespace(client.id, false)
- local ns_pull = vim.lsp.diagnostic.get_namespace(client.id, true)
+ local ns_push = lsp.diagnostic.get_namespace(client.id, false)
+ local ns_pull = lsp.diagnostic.get_namespace(client.id, true)
local diagnostics = {}
local lnum = api.nvim_win_get_cursor(0)[1] - 1
vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum }))
@@ -921,20 +1257,20 @@ function M.code_action(opts)
end
end
+--- @deprecated
--- Executes an LSP server command.
--- @param command_params lsp.ExecuteCommandParams
--- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
function M.execute_command(command_params)
- validate({
- command = { command_params.command, 's' },
- arguments = { command_params.arguments, 't', true },
- })
+ validate('command', command_params.command, 'string')
+ validate('arguments', command_params.arguments, 'table', true)
+ vim.deprecate('execute_command', 'client:exec_cmd', '0.12')
command_params = {
command = command_params.command,
arguments = command_params.arguments,
workDoneToken = command_params.workDoneToken,
}
- request(ms.workspace_executeCommand, command_params)
+ lsp.buf_request(0, ms.workspace_executeCommand, command_params)
end
return M
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index e3c82f4169..11ecb87507 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -91,7 +91,7 @@ local validate = vim.validate
--- (default: client-id)
--- @field name? string
---
---- Language ID as string. Defaults to the filetype.
+--- Language ID as string. Defaults to the buffer filetype.
--- @field get_language_id? fun(bufnr: integer, filetype: string): string
---
--- The encoding that the LSP server expects. Client does not verify this is correct.
@@ -216,6 +216,7 @@ local validate = vim.validate
---
--- The capabilities provided by the client (editor or tool)
--- @field capabilities lsp.ClientCapabilities
+--- @field private registrations table<string,lsp.Registration[]>
--- @field dynamic_capabilities lsp.DynamicCapabilities
---
--- Sends a request to the server.
@@ -291,7 +292,7 @@ local client_index = 0
--- @param filename (string) path to check
--- @return boolean # true if {filename} exists and is a directory, false otherwise
local function is_dir(filename)
- validate({ filename = { filename, 's' } })
+ validate('filename', filename, 'string')
local stat = uv.fs_stat(filename)
return stat and stat.type == 'directory' or false
end
@@ -312,9 +313,7 @@ local valid_encodings = {
--- @param encoding string? Encoding to normalize
--- @return string # normalized encoding name
local function validate_encoding(encoding)
- validate({
- encoding = { encoding, 's', true },
- })
+ validate('encoding', encoding, 'string', true)
if not encoding then
return valid_encodings.UTF16
end
@@ -350,27 +349,23 @@ end
--- Validates a client configuration as given to |vim.lsp.start_client()|.
--- @param config vim.lsp.ClientConfig
local function validate_config(config)
- validate({
- config = { config, 't' },
- })
- validate({
- handlers = { config.handlers, 't', true },
- capabilities = { config.capabilities, 't', true },
- cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
- cmd_env = { config.cmd_env, 't', true },
- detached = { config.detached, 'b', true },
- name = { config.name, 's', true },
- on_error = { config.on_error, 'f', true },
- on_exit = { config.on_exit, { 'f', 't' }, true },
- on_init = { config.on_init, { 'f', 't' }, true },
- on_attach = { config.on_attach, { 'f', 't' }, true },
- settings = { config.settings, 't', true },
- commands = { config.commands, 't', true },
- before_init = { config.before_init, { 'f', 't' }, true },
- offset_encoding = { config.offset_encoding, 's', true },
- flags = { config.flags, 't', true },
- get_language_id = { config.get_language_id, 'f', true },
- })
+ validate('config', config, 'table')
+ validate('handlers', config.handlers, 'table', true)
+ validate('capabilities', config.capabilities, 'table', true)
+ validate('cmd_cwd', config.cmd_cwd, optional_validator(is_dir), 'directory')
+ validate('cmd_env', config.cmd_env, 'table', true)
+ validate('detached', config.detached, 'boolean', true)
+ validate('name', config.name, 'string', true)
+ validate('on_error', config.on_error, 'function', true)
+ validate('on_exit', config.on_exit, { 'function', 'table' }, true)
+ validate('on_init', config.on_init, { 'function', 'table' }, true)
+ validate('on_attach', config.on_attach, { 'function', 'table' }, true)
+ validate('settings', config.settings, 'table', true)
+ validate('commands', config.commands, 'table', true)
+ validate('before_init', config.before_init, { 'function', 'table' }, true)
+ validate('offset_encoding', config.offset_encoding, 'string', true)
+ validate('flags', config.flags, 'table', true)
+ validate('get_language_id', config.get_language_id, 'function', true)
assert(
(
@@ -409,18 +404,16 @@ local function get_name(id, config)
return tostring(id)
end
---- @param workspace_folders lsp.WorkspaceFolder[]?
---- @param root_dir string?
+--- @param workspace_folders string|lsp.WorkspaceFolder[]?
--- @return lsp.WorkspaceFolder[]?
-local function get_workspace_folders(workspace_folders, root_dir)
- if workspace_folders then
+local function get_workspace_folders(workspace_folders)
+ if type(workspace_folders) == 'table' then
return workspace_folders
- end
- if root_dir then
+ elseif type(workspace_folders) == 'string' then
return {
{
- uri = vim.uri_from_fname(root_dir),
- name = root_dir,
+ uri = vim.uri_from_fname(workspace_folders),
+ name = workspace_folders,
},
}
end
@@ -457,13 +450,13 @@ function Client.create(config)
requests = {},
attached_buffers = {},
server_capabilities = {},
- dynamic_capabilities = lsp._dynamic.new(id),
+ registrations = {},
commands = config.commands or {},
settings = config.settings or {},
flags = config.flags or {},
get_language_id = config.get_language_id or default_get_language_id,
capabilities = config.capabilities or lsp.protocol.make_client_capabilities(),
- workspace_folders = get_workspace_folders(config.workspace_folders, config.root_dir),
+ workspace_folders = get_workspace_folders(config.workspace_folders or config.root_dir),
root_dir = config.root_dir,
_before_init_cb = config.before_init,
_on_init_cbs = ensure_list(config.on_init),
@@ -484,6 +477,28 @@ function Client.create(config)
messages = { name = name, messages = {}, progress = {}, status = {} },
}
+ --- @class lsp.DynamicCapabilities
+ --- @nodoc
+ self.dynamic_capabilities = {
+ capabilities = self.registrations,
+ client_id = id,
+ register = function(_, registrations)
+ return self:_register_dynamic(registrations)
+ end,
+ unregister = function(_, unregistrations)
+ return self:_unregister_dynamic(unregistrations)
+ end,
+ get = function(_, method, opts)
+ return self:_get_registration(method, opts and opts.bufnr)
+ end,
+ supports_registration = function(_, method)
+ return self:_supports_registration(method)
+ end,
+ supports = function(_, method, opts)
+ return self:_get_registration(method, opts and opts.bufnr) ~= nil
+ end,
+ }
+
self.request = method_wrapper(self, Client._request)
self.request_sync = method_wrapper(self, Client._request_sync)
self.notify = method_wrapper(self, Client._notify)
@@ -640,7 +655,7 @@ end
--- @param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
--- @return integer bufnr
local function resolve_bufnr(bufnr)
- validate({ bufnr = { bufnr, 'n', true } })
+ validate('bufnr', bufnr, 'number', true)
if bufnr == nil or bufnr == 0 then
return api.nvim_get_current_buf()
end
@@ -806,7 +821,7 @@ end
--- @return boolean status true if notification was successful. false otherwise
--- @see |vim.lsp.client.notify()|
function Client:_cancel_request(id)
- validate({ id = { id, 'n' } })
+ validate('id', id, 'number')
local request = self.requests[id]
if request and request.type == 'pending' then
request.type = 'cancel'
@@ -852,6 +867,105 @@ function Client:_stop(force)
end)
end
+--- Get options for a method that is registered dynamically.
+--- @param method string
+function Client:_supports_registration(method)
+ local capability = vim.tbl_get(self.capabilities, unpack(vim.split(method, '/')))
+ return type(capability) == 'table' and capability.dynamicRegistration
+end
+
+--- @private
+--- @param registrations lsp.Registration[]
+function Client:_register_dynamic(registrations)
+ -- remove duplicates
+ self:_unregister_dynamic(registrations)
+ for _, reg in ipairs(registrations) do
+ local method = reg.method
+ if not self.registrations[method] then
+ self.registrations[method] = {}
+ end
+ table.insert(self.registrations[method], reg)
+ end
+end
+
+--- @param registrations lsp.Registration[]
+function Client:_register(registrations)
+ self:_register_dynamic(registrations)
+
+ local unsupported = {} --- @type string[]
+
+ for _, reg in ipairs(registrations) do
+ local method = reg.method
+ if method == ms.workspace_didChangeWatchedFiles then
+ vim.lsp._watchfiles.register(reg, self.id)
+ elseif not self:_supports_registration(method) then
+ unsupported[#unsupported + 1] = method
+ end
+ end
+
+ if #unsupported > 0 then
+ local warning_tpl = 'The language server %s triggers a registerCapability '
+ .. 'handler for %s despite dynamicRegistration set to false. '
+ .. 'Report upstream, this warning is harmless'
+ log.warn(string.format(warning_tpl, self.name, table.concat(unsupported, ', ')))
+ end
+end
+
+--- @private
+--- @param unregistrations lsp.Unregistration[]
+function Client:_unregister_dynamic(unregistrations)
+ for _, unreg in ipairs(unregistrations) do
+ local sreg = self.registrations[unreg.method]
+ -- Unegister dynamic capability
+ for i, reg in ipairs(sreg or {}) do
+ if reg.id == unreg.id then
+ table.remove(sreg, i)
+ break
+ end
+ end
+ end
+end
+
+--- @param unregistrations lsp.Unregistration[]
+function Client:_unregister(unregistrations)
+ self:_unregister_dynamic(unregistrations)
+ for _, unreg in ipairs(unregistrations) do
+ if unreg.method == ms.workspace_didChangeWatchedFiles then
+ vim.lsp._watchfiles.unregister(unreg, self.id)
+ end
+ end
+end
+
+--- @private
+function Client:_get_language_id(bufnr)
+ return self.get_language_id(bufnr, vim.bo[bufnr].filetype)
+end
+
+--- @param method string
+--- @param bufnr? integer
+--- @return lsp.Registration?
+function Client:_get_registration(method, bufnr)
+ bufnr = bufnr or vim.api.nvim_get_current_buf()
+ for _, reg in ipairs(self.registrations[method] or {}) do
+ if not reg.registerOptions or not reg.registerOptions.documentSelector then
+ return reg
+ end
+ local documentSelector = reg.registerOptions.documentSelector
+ local language = self:_get_language_id(bufnr)
+ local uri = vim.uri_from_bufnr(bufnr)
+ local fname = vim.uri_to_fname(uri)
+ for _, filter in ipairs(documentSelector) do
+ if
+ not (filter.language and language ~= filter.language)
+ and not (filter.scheme and not vim.startswith(uri, filter.scheme .. ':'))
+ and not (filter.pattern and not vim.glob.to_lpeg(filter.pattern):match(fname))
+ then
+ return reg
+ end
+ end
+ end
+end
+
--- @private
--- Checks whether a client is stopped.
---
@@ -865,10 +979,9 @@ end
--- or via workspace/executeCommand (if supported by the server)
---
--- @param command lsp.Command
---- @param context? {bufnr: integer}
+--- @param context? {bufnr?: integer}
--- @param handler? lsp.Handler only called if a server command
---- @param on_unsupported? function handler invoked when the command is not supported by the client.
-function Client:_exec_cmd(command, context, handler, on_unsupported)
+function Client:exec_cmd(command, context, handler)
context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]]
context.bufnr = context.bufnr or api.nvim_get_current_buf()
context.client_id = self.id
@@ -881,25 +994,23 @@ function Client:_exec_cmd(command, context, handler, on_unsupported)
local command_provider = self.server_capabilities.executeCommandProvider
local commands = type(command_provider) == 'table' and command_provider.commands or {}
+
if not vim.list_contains(commands, cmdname) then
- if on_unsupported then
- on_unsupported()
- else
- vim.notify_once(
- string.format(
- 'Language server `%s` does not support command `%s`. This command may require a client extension.',
- self.name,
- cmdname
- ),
- vim.log.levels.WARN
- )
- end
+ vim.notify_once(
+ string.format(
+ 'Language server `%s` does not support command `%s`. This command may require a client extension.',
+ self.name,
+ cmdname
+ ),
+ vim.log.levels.WARN
+ )
return
end
-- Not using command directly to exclude extra properties,
-- see https://github.com/python-lsp/python-lsp-server/issues/146
+ --- @type lsp.ExecuteCommandParams
local params = {
- command = command.command,
+ command = cmdname,
arguments = command.arguments,
}
self.request(ms.workspace_executeCommand, params, handler, context.bufnr)
@@ -917,12 +1028,11 @@ function Client:_text_document_did_open_handler(bufnr)
return
end
- local filetype = vim.bo[bufnr].filetype
self.notify(ms.textDocument_didOpen, {
textDocument = {
version = lsp.util.buf_versions[bufnr],
uri = vim.uri_from_bufnr(bufnr),
- languageId = self.get_language_id(bufnr, filetype),
+ languageId = self:_get_language_id(bufnr),
text = lsp._buf_get_full_text(bufnr),
},
})
@@ -987,12 +1097,37 @@ function Client:_supports_method(method, opts)
if vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
return true
end
- if self.dynamic_capabilities:supports_registration(method) then
- return self.dynamic_capabilities:supports(method, opts)
+
+ local rmethod = lsp._resolve_to_request[method]
+ if rmethod then
+ if self:_supports_registration(rmethod) then
+ local reg = self:_get_registration(rmethod, opts and opts.bufnr)
+ return vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider') or false
+ end
+ else
+ if self:_supports_registration(method) then
+ return self:_get_registration(method, opts and opts.bufnr) ~= nil
+ end
end
return false
end
+--- Get options for a method that is registered dynamically.
+--- @param method string
+--- @param bufnr? integer
+--- @return lsp.LSPAny?
+function Client:_get_registration_options(method, bufnr)
+ if not self:_supports_registration(method) then
+ return
+ end
+
+ local reg = self:_get_registration(method, bufnr)
+
+ if reg then
+ return reg.registerOptions
+ end
+end
+
--- @private
--- Handles a notification sent by an LSP server by invoking the
--- corresponding handler.
@@ -1070,7 +1205,7 @@ function Client:_add_workspace_folder(dir)
end
end
- local wf = assert(get_workspace_folders(nil, dir))
+ local wf = assert(get_workspace_folders(dir))
self:_notify(ms.workspace_didChangeWorkspaceFolders, {
event = { added = wf, removed = {} },
@@ -1085,7 +1220,7 @@ end
--- Remove a directory to the workspace folders.
--- @param dir string?
function Client:_remove_workspace_folder(dir)
- local wf = assert(get_workspace_folders(nil, dir))
+ local wf = assert(get_workspace_folders(dir))
self:_notify(ms.workspace_didChangeWorkspaceFolders, {
event = { added = {}, removed = wf },
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index c1b6bfb28c..fdbdda695a 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -48,7 +48,7 @@ local function execute_lens(lens, bufnr, client_id)
local client = vim.lsp.get_client_by_id(client_id)
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
- client:_exec_cmd(lens.command, { bufnr = bufnr }, function(...)
+ client:exec_cmd(lens.command, { bufnr = bufnr }, function(...)
vim.lsp.handlers[ms.workspace_executeCommand](...)
M.refresh()
end)
@@ -261,7 +261,7 @@ end
---@param err lsp.ResponseError?
---@param result lsp.CodeLens[]
---@param ctx lsp.HandlerContext
-function M.on_codelens(err, result, ctx, _)
+function M.on_codelens(err, result, ctx)
if err then
active_refreshes[assert(ctx.bufnr)] = nil
log.error('codelens', err)
diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua
index 71ea2df100..92bc110a97 100644
--- a/runtime/lua/vim/lsp/completion.lua
+++ b/runtime/lua/vim/lsp/completion.lua
@@ -113,12 +113,11 @@ local function parse_snippet(input)
end
--- @param item lsp.CompletionItem
---- @param suffix? string
-local function apply_snippet(item, suffix)
+local function apply_snippet(item)
if item.textEdit then
- vim.snippet.expand(item.textEdit.newText .. suffix)
+ vim.snippet.expand(item.textEdit.newText)
elseif item.insertText then
- vim.snippet.expand(item.insertText .. suffix)
+ vim.snippet.expand(item.insertText)
end
end
@@ -221,6 +220,20 @@ local function get_doc(item)
return ''
end
+---@param value string
+---@param prefix string
+---@return boolean
+local function match_item_by_value(value, prefix)
+ if vim.o.completeopt:find('fuzzy') ~= nil then
+ return next(vim.fn.matchfuzzy({ value }, prefix)) ~= nil
+ end
+
+ if vim.o.ignorecase and (not vim.o.smartcase or not prefix:find('%u')) then
+ return vim.startswith(value:lower(), prefix:lower())
+ end
+ return vim.startswith(value, prefix)
+end
+
--- Turns the result of a `textDocument/completion` request into vim-compatible
--- |complete-items|.
---
@@ -245,8 +258,16 @@ function M._lsp_to_complete_items(result, prefix, client_id)
else
---@param item lsp.CompletionItem
matches = function(item)
- local text = item.filterText or item.label
- return next(vim.fn.matchfuzzy({ text }, prefix)) ~= nil
+ if item.filterText then
+ return match_item_by_value(item.filterText, prefix)
+ end
+
+ if item.textEdit then
+ -- server took care of filtering
+ return true
+ end
+
+ return match_item_by_value(item.label, prefix)
end
end
@@ -272,7 +293,7 @@ function M._lsp_to_complete_items(result, prefix, client_id)
icase = 1,
dup = 1,
empty = 1,
- hl_group = hl_group,
+ abbr_hlgroup = hl_group,
user_data = {
nvim = {
lsp = {
@@ -316,7 +337,7 @@ local function adjust_start_col(lnum, line, items, encoding)
end
end
if min_start_char then
- return lsp.util._str_byteindex_enc(line, min_start_char, encoding)
+ return vim.str_byteindex(line, encoding, min_start_char, false)
else
return nil
end
@@ -539,35 +560,24 @@ local function on_complete_done()
-- Remove the already inserted word.
local start_char = cursor_col - #completed_item.word
- local line = api.nvim_buf_get_lines(bufnr, cursor_row, cursor_row + 1, true)[1]
- api.nvim_buf_set_text(bufnr, cursor_row, start_char, cursor_row, #line, { '' })
- return line:sub(cursor_col + 1)
+ api.nvim_buf_set_text(bufnr, cursor_row, start_char, cursor_row, cursor_col, { '' })
end
- --- @param suffix? string
- local function apply_snippet_and_command(suffix)
+ local function apply_snippet_and_command()
if expand_snippet then
- apply_snippet(completion_item, suffix)
+ apply_snippet(completion_item)
end
local command = completion_item.command
if command then
- client:_exec_cmd(command, { bufnr = bufnr }, nil, function()
- vim.lsp.log.warn(
- string.format(
- 'Language server `%s` does not support command `%s`. This command may require a client extension.',
- client.name,
- command.command
- )
- )
- end)
+ client:exec_cmd(command, { bufnr = bufnr })
end
end
if completion_item.additionalTextEdits and next(completion_item.additionalTextEdits) then
- local suffix = clear_word()
+ clear_word()
lsp.util.apply_text_edits(completion_item.additionalTextEdits, bufnr, offset_encoding)
- apply_snippet_and_command(suffix)
+ apply_snippet_and_command()
elseif resolve_provider and type(completion_item) == 'table' then
local changedtick = vim.b[bufnr].changedtick
@@ -577,7 +587,7 @@ local function on_complete_done()
return
end
- local suffix = clear_word()
+ clear_word()
if err then
vim.notify_once(err.message, vim.log.levels.WARN)
elseif result and result.additionalTextEdits then
@@ -587,16 +597,16 @@ local function on_complete_done()
end
end
- apply_snippet_and_command(suffix)
+ apply_snippet_and_command()
end, bufnr)
else
- local suffix = clear_word()
- apply_snippet_and_command(suffix)
+ clear_word()
+ apply_snippet_and_command()
end
end
--- @class vim.lsp.completion.BufferOpts
---- @field autotrigger? boolean Whether to trigger completion automatically. Default: false
+--- @field autotrigger? boolean Default: false When true, completion triggers automatically based on the server's `triggerCharacters`.
--- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|.
---@param client_id integer
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index c10312484b..8fd30c7668 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -9,14 +9,6 @@ local augroup = api.nvim_create_augroup('vim_lsp_diagnostic', {})
local DEFAULT_CLIENT_ID = -1
-local function get_client_id(client_id)
- if client_id == nil then
- client_id = DEFAULT_CLIENT_ID
- end
-
- return client_id
-end
-
---@param severity lsp.DiagnosticSeverity
local function severity_lsp_to_vim(severity)
if type(severity) == 'string' then
@@ -33,25 +25,6 @@ local function severity_vim_to_lsp(severity)
return severity
end
----@param lines string[]?
----@param lnum integer
----@param col integer
----@param offset_encoding string
----@return integer
-local function line_byte_from_position(lines, lnum, col, offset_encoding)
- if not lines or offset_encoding == 'utf-8' then
- return col
- end
-
- local line = lines[lnum + 1]
- local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
- if ok then
- return result --- @type integer
- end
-
- return col
-end
-
---@param bufnr integer
---@return string[]?
local function get_buf_lines(bufnr)
@@ -118,12 +91,13 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
)
message = diagnostic.message.value
end
+ local line = buf_lines and buf_lines[start.line + 1] or ''
--- @type vim.Diagnostic
return {
lnum = start.line,
- col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
+ col = vim.str_byteindex(line, offset_encoding, start.character, false),
end_lnum = _end.line,
- end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding),
+ end_col = vim.str_byteindex(line, offset_encoding, _end.character, false),
severity = severity_lsp_to_vim(diagnostic.severity),
message = message,
source = diagnostic.source,
@@ -195,7 +169,7 @@ local _client_pull_namespaces = {}
---@param client_id integer The id of the LSP client
---@param is_pull boolean? Whether the namespace is for a pull or push client. Defaults to push
function M.get_namespace(client_id, is_pull)
- vim.validate({ client_id = { client_id, 'n' } })
+ vim.validate('client_id', client_id, 'number')
local client = vim.lsp.get_client_by_id(client_id)
if is_pull then
@@ -236,8 +210,7 @@ end
--- @param client_id? integer
--- @param diagnostics vim.Diagnostic[]
--- @param is_pull boolean
---- @param config? vim.diagnostic.Opts
-local function handle_diagnostics(uri, client_id, diagnostics, is_pull, config)
+local function handle_diagnostics(uri, client_id, diagnostics, is_pull)
local fname = vim.uri_to_fname(uri)
if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
@@ -249,91 +222,39 @@ local function handle_diagnostics(uri, client_id, diagnostics, is_pull, config)
return
end
- client_id = get_client_id(client_id)
- local namespace = M.get_namespace(client_id, is_pull)
-
- if config then
- --- @cast config table<string, table>
- for _, opt in pairs(config) do
- convert_severity(opt)
- end
- -- Persist configuration to ensure buffer reloads use the same
- -- configuration. To make lsp.with configuration work (See :help
- -- lsp-handler-configuration)
- vim.diagnostic.config(config, namespace)
+ if client_id == nil then
+ client_id = DEFAULT_CLIENT_ID
end
+ local namespace = M.get_namespace(client_id, is_pull)
+
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
end
--- |lsp-handler| for the method "textDocument/publishDiagnostics"
---
---- See |vim.diagnostic.config()| for configuration options. Handler-specific
---- configuration can be set using |vim.lsp.with()|:
----
---- ```lua
---- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
---- vim.lsp.diagnostic.on_publish_diagnostics, {
---- -- Enable underline, use default values
---- underline = true,
---- -- Enable virtual text, override spacing to 4
---- virtual_text = {
---- spacing = 4,
---- },
---- -- Use a function to dynamically turn signs off
---- -- and on, using buffer local variables
---- signs = function(namespace, bufnr)
---- return vim.b[bufnr].show_signs == true
---- end,
---- -- Disable a feature
---- update_in_insert = false,
---- }
---- )
---- ```
+--- See |vim.diagnostic.config()| for configuration options.
---
---@param _ lsp.ResponseError?
---@param result lsp.PublishDiagnosticsParams
---@param ctx lsp.HandlerContext
----@param config? vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
-function M.on_publish_diagnostics(_, result, ctx, config)
- handle_diagnostics(result.uri, ctx.client_id, result.diagnostics, false, config)
+function M.on_publish_diagnostics(_, result, ctx)
+ handle_diagnostics(result.uri, ctx.client_id, result.diagnostics, false)
end
--- |lsp-handler| for the method "textDocument/diagnostic"
---
---- See |vim.diagnostic.config()| for configuration options. Handler-specific
---- configuration can be set using |vim.lsp.with()|:
----
---- ```lua
---- vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with(
---- vim.lsp.diagnostic.on_diagnostic, {
---- -- Enable underline, use default values
---- underline = true,
---- -- Enable virtual text, override spacing to 4
---- virtual_text = {
---- spacing = 4,
---- },
---- -- Use a function to dynamically turn signs off
---- -- and on, using buffer local variables
---- signs = function(namespace, bufnr)
---- return vim.b[bufnr].show_signs == true
---- end,
---- -- Disable a feature
---- update_in_insert = false,
---- }
---- )
---- ```
+--- See |vim.diagnostic.config()| for configuration options.
---
---@param _ lsp.ResponseError?
---@param result lsp.DocumentDiagnosticReport
---@param ctx lsp.HandlerContext
----@param config vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
-function M.on_diagnostic(_, result, ctx, config)
+function M.on_diagnostic(_, result, ctx)
if result == nil or result.kind == 'unchanged' then
return
end
- handle_diagnostics(ctx.params.textDocument.uri, ctx.client_id, result.items, true, config)
+ handle_diagnostics(ctx.params.textDocument.uri, ctx.client_id, result.items, true)
end
--- Clear push diagnostics and diagnostic cache.
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 44548fec92..5c28d88b38 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -5,10 +5,21 @@ local util = require('vim.lsp.util')
local api = vim.api
local completion = require('vim.lsp.completion')
---- @type table<string,lsp.Handler>
+--- @type table<string, lsp.Handler>
local M = {}
--- FIXME: DOC: Expose in vimdocs
+--- @deprecated
+--- Client to server response handlers.
+--- @type table<vim.lsp.protocol.Method.ClientToServer, lsp.Handler>
+local RCS = {}
+
+--- Server to client request handlers.
+--- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
+local RSC = {}
+
+--- Server to client notification handlers.
+--- @type table<vim.lsp.protocol.Method.ServerToClient, lsp.Handler>
+local NSC = {}
--- Writes to error buffer.
---@param ... string Will be concatenated before being written
@@ -18,14 +29,15 @@ local function err_message(...)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
-M[ms.workspace_executeCommand] = function(_, _, _, _)
+RCS[ms.workspace_executeCommand] = function(_, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
---@param params lsp.ProgressParams
---@param ctx lsp.HandlerContext
-M[ms.dollar_progress] = function(_, params, ctx)
+---@diagnostic disable-next-line:no-unknown
+RSC[ms.dollar_progress] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
@@ -59,26 +71,26 @@ M[ms.dollar_progress] = function(_, params, ctx)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
----@param result lsp.WorkDoneProgressCreateParams
+---@param params lsp.WorkDoneProgressCreateParams
---@param ctx lsp.HandlerContext
-M[ms.window_workDoneProgress_create] = function(_, result, ctx)
+RSC[ms.window_workDoneProgress_create] = function(_, params, ctx)
local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- client.progress:push(result)
+ client.progress:push(params)
return vim.NIL
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
----@param result lsp.ShowMessageRequestParams
-M[ms.window_showMessageRequest] = function(_, result)
- local actions = result.actions or {}
+---@param params lsp.ShowMessageRequestParams
+RSC[ms.window_showMessageRequest] = function(_, params)
+ local actions = params.actions or {}
local co, is_main = coroutine.running()
if co and not is_main then
local opts = {
- prompt = result.message .. ': ',
+ prompt = params.message .. ': ',
format_item = function(action)
return (action.title:gsub('\r\n', '\\r\\n')):gsub('\n', '\\n')
end,
@@ -92,7 +104,7 @@ M[ms.window_showMessageRequest] = function(_, result)
end)
return coroutine.yield()
else
- local option_strings = { result.message, '\nRequest Actions:' }
+ local option_strings = { params.message, '\nRequest Actions:' }
for i, action in ipairs(actions) do
local title = action.title:gsub('\r\n', '\\r\\n')
title = title:gsub('\n', '\\n')
@@ -108,65 +120,37 @@ M[ms.window_showMessageRequest] = function(_, result)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
---- @param result lsp.RegistrationParams
-M[ms.client_registerCapability] = function(_, result, ctx)
- local client_id = ctx.client_id
- local client = assert(vim.lsp.get_client_by_id(client_id))
-
- client.dynamic_capabilities:register(result.registrations)
- for bufnr, _ in pairs(client.attached_buffers) do
+--- @param params lsp.RegistrationParams
+RSC[ms.client_registerCapability] = function(_, params, ctx)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ client:_register(params.registrations)
+ for bufnr in pairs(client.attached_buffers) do
vim.lsp._set_defaults(client, bufnr)
end
-
- ---@type string[]
- local unsupported = {}
- for _, reg in ipairs(result.registrations) do
- if reg.method == ms.workspace_didChangeWatchedFiles then
- vim.lsp._watchfiles.register(reg, ctx)
- elseif not client.dynamic_capabilities:supports_registration(reg.method) then
- unsupported[#unsupported + 1] = reg.method
- end
- end
- if #unsupported > 0 then
- local warning_tpl = 'The language server %s triggers a registerCapability '
- .. 'handler for %s despite dynamicRegistration set to false. '
- .. 'Report upstream, this warning is harmless'
- local client_name = client and client.name or string.format('id=%d', client_id)
- local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', '))
- log.warn(warning)
- end
return vim.NIL
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
---- @param result lsp.UnregistrationParams
-M[ms.client_unregisterCapability] = function(_, result, ctx)
- local client_id = ctx.client_id
- local client = assert(vim.lsp.get_client_by_id(client_id))
- client.dynamic_capabilities:unregister(result.unregisterations)
-
- for _, unreg in ipairs(result.unregisterations) do
- if unreg.method == ms.workspace_didChangeWatchedFiles then
- vim.lsp._watchfiles.unregister(unreg, ctx)
- end
- end
+--- @param params lsp.UnregistrationParams
+RSC[ms.client_unregisterCapability] = function(_, params, ctx)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ client:_unregister(params.unregisterations)
return vim.NIL
end
+-- TODO(lewis6991): Do we need to notify other servers?
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
+RSC[ms.workspace_applyEdit] = function(_, params, ctx)
assert(
- workspace_edit,
+ params,
'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
)
-- TODO(ashkan) Do something more with label?
- local client_id = ctx.client_id
- local client = assert(vim.lsp.get_client_by_id(client_id))
- if workspace_edit.label then
- print('Workspace edit', workspace_edit.label)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ if params.label then
+ print('Workspace edit', params.label)
end
- local status, result =
- pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding)
+ local status, result = pcall(util.apply_workspace_edit, params.edit, client.offset_encoding)
return {
applied = status,
failureReason = result,
@@ -182,24 +166,23 @@ local function lookup_section(table, section)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
---- @param result lsp.ConfigurationParams
-M[ms.workspace_configuration] = function(_, result, ctx)
- local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
+--- @param params lsp.ConfigurationParams
+RSC[ms.workspace_configuration] = function(_, params, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
err_message(
'LSP[',
- client_id,
+ ctx.client_id,
'] client has shut down after sending a workspace/configuration request'
)
return
end
- if not result.items then
+ if not params.items then
return {}
end
local response = {}
- for _, item in ipairs(result.items) do
+ for _, item in ipairs(params.items) do
if item.section then
local value = lookup_section(client.settings, item.section)
-- For empty sections with no explicit '' key, return settings as is
@@ -216,57 +199,34 @@ M[ms.workspace_configuration] = function(_, result, ctx)
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
-M[ms.workspace_workspaceFolders] = function(_, _, ctx)
- local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
+RSC[ms.workspace_workspaceFolders] = function(_, _, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[id=', client_id, '] client has shut down after sending the message')
+ err_message('LSP[id=', ctx.client_id, '] client has shut down after sending the message')
return
end
return client.workspace_folders or vim.NIL
end
-M[ms.textDocument_publishDiagnostics] = function(...)
+NSC[ms.textDocument_publishDiagnostics] = function(...)
return vim.lsp.diagnostic.on_publish_diagnostics(...)
end
-M[ms.textDocument_diagnostic] = function(...)
+--- @private
+RCS[ms.textDocument_diagnostic] = function(...)
return vim.lsp.diagnostic.on_diagnostic(...)
end
-M[ms.textDocument_codeLens] = function(...)
+--- @private
+RCS[ms.textDocument_codeLens] = function(...)
return vim.lsp.codelens.on_codelens(...)
end
-M[ms.textDocument_inlayHint] = function(...)
+--- @private
+RCS[ms.textDocument_inlayHint] = function(...)
return vim.lsp.inlay_hint.on_inlayhint(...)
end
---- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
-M[ms.textDocument_references] = function(_, result, ctx, config)
- if not result or vim.tbl_isempty(result) then
- vim.notify('No references found')
- return
- end
-
- local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
- config = config or {}
- local title = 'References'
- local items = util.locations_to_items(result, client.offset_encoding)
-
- local list = { title = title, items = items, context = ctx }
- if config.loclist then
- vim.fn.setloclist(0, {}, ' ', list)
- vim.cmd.lopen()
- elseif config.on_list then
- assert(vim.is_callable(config.on_list), 'on_list is not a function')
- config.on_list(list)
- else
- vim.fn.setqflist({}, ' ', list)
- vim.cmd('botright copen')
- end
-end
-
--- Return a function that converts LSP responses to list items and opens the list
---
--- The returned function has an optional {config} parameter that accepts |vim.lsp.ListOpts|
@@ -276,6 +236,7 @@ end
---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
---@return lsp.Handler
local function response_to_list(map_result, entity, title_fn)
+ --- @diagnostic disable-next-line:redundant-parameter
return function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No ' .. entity .. ' found')
@@ -299,8 +260,9 @@ local function response_to_list(map_result, entity, title_fn)
end
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
-M[ms.textDocument_documentSymbol] = response_to_list(
+RCS[ms.textDocument_documentSymbol] = response_to_list(
util.symbols_to_items,
'document symbols',
function(ctx)
@@ -309,13 +271,15 @@ M[ms.textDocument_documentSymbol] = response_to_list(
end
)
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
-M[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
+RCS[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
return string.format("Symbols matching '%s'", ctx.params.query)
end)
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
-M[ms.textDocument_rename] = function(_, result, ctx, _)
+RCS[ms.textDocument_rename] = function(_, result, ctx)
if not result then
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
return
@@ -324,8 +288,9 @@ M[ms.textDocument_rename] = function(_, result, ctx, _)
util.apply_workspace_edit(result, client.offset_encoding)
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
-M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
+RCS[ms.textDocument_rangeFormatting] = function(_, result, ctx)
if not result then
return
end
@@ -333,8 +298,9 @@ M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
-M[ms.textDocument_formatting] = function(_, result, ctx, _)
+RCS[ms.textDocument_formatting] = function(_, result, ctx)
if not result then
return
end
@@ -342,8 +308,9 @@ M[ms.textDocument_formatting] = function(_, result, ctx, _)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-M[ms.textDocument_completion] = function(_, result, _, _)
+RCS[ms.textDocument_completion] = function(_, result, _)
if vim.tbl_isempty(result or {}) then
return
end
@@ -358,6 +325,7 @@ M[ms.textDocument_completion] = function(_, result, _, _)
vim.fn.complete(textMatch + 1, matches)
end
+--- @deprecated
--- |lsp-handler| for the method "textDocument/hover"
---
--- ```lua
@@ -378,6 +346,7 @@ end
--- - border: (default=nil)
--- - Add borders to the floating window
--- - See |vim.lsp.util.open_floating_preview()| for more options.
+--- @diagnostic disable-next-line:redundant-parameter
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -408,60 +377,14 @@ function M.hover(_, result, ctx, config)
return util.open_floating_preview(contents, format, config)
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
-M[ms.textDocument_hover] = M.hover
-
---- Jumps to a location. Used as a handler for multiple LSP methods.
----@param _ nil not used
----@param result (table) result of LSP method; a location or a list of locations.
----@param ctx (lsp.HandlerContext) table containing the context of the request, including the method
----@param config? vim.lsp.LocationOpts
----(`textDocument/definition` can return `Location` or `Location[]`
-local function location_handler(_, result, ctx, config)
- if result == nil or vim.tbl_isempty(result) then
- log.info(ctx.method, 'No location found')
- return nil
- end
- local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
-
- config = config or {}
-
- -- textDocument/definition can return Location or Location[]
- -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
- if not vim.islist(result) then
- result = { result }
- end
+--- @diagnostic disable-next-line: deprecated
+RCS[ms.textDocument_hover] = M.hover
- local title = 'LSP locations'
- local items = util.locations_to_items(result, client.offset_encoding)
-
- if config.on_list then
- assert(vim.is_callable(config.on_list), 'on_list is not a function')
- config.on_list({ title = title, items = items })
- return
- end
- if #result == 1 then
- util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
- return
- end
- if config.loclist then
- vim.fn.setloclist(0, {}, ' ', { title = title, items = items })
- vim.cmd.lopen()
- else
- vim.fn.setqflist({}, ' ', { title = title, items = items })
- vim.cmd('botright copen')
- end
-end
-
---- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
-M[ms.textDocument_declaration] = location_handler
---- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
-M[ms.textDocument_definition] = location_handler
---- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
-M[ms.textDocument_typeDefinition] = location_handler
---- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
-M[ms.textDocument_implementation] = location_handler
+local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
+--- @deprecated remove in 0.13
--- |lsp-handler| for the method "textDocument/signatureHelp".
---
--- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
@@ -476,12 +399,13 @@ M[ms.textDocument_implementation] = location_handler
--- ```
---
---@param _ lsp.ResponseError?
----@param result lsp.SignatureHelp Response from the language server
+---@param result lsp.SignatureHelp? Response from the language server
---@param ctx lsp.HandlerContext Client context
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
--- - See |vim.lsp.util.open_floating_preview()| for more options
+--- @diagnostic disable-next-line:redundant-parameter
function M.signature_help(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -509,19 +433,27 @@ function M.signature_help(_, result, ctx, config)
return
end
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
+ -- Highlight the active parameter.
if hl then
- -- Highlight the second line if the signature is wrapped in a Markdown code block.
- local line = vim.startswith(lines[1], '```') and 1 or 0
- api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', line, unpack(hl))
+ vim.hl.range(
+ fbuf,
+ sig_help_ns,
+ 'LspSignatureActiveParameter',
+ { hl[1], hl[2] },
+ { hl[3], hl[4] }
+ )
end
return fbuf, fwin
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
-M[ms.textDocument_signatureHelp] = M.signature_help
+--- @diagnostic disable-next-line:deprecated
+RCS[ms.textDocument_signatureHelp] = M.signature_help
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
-M[ms.textDocument_documentHighlight] = function(_, result, ctx, _)
+RCS[ms.textDocument_documentHighlight] = function(_, result, ctx)
if not result then
return
end
@@ -564,11 +496,13 @@ local function make_call_hierarchy_handler(direction)
end
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
-M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
+RCS[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
-M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
+RCS[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
--- Displays type hierarchy in the quickfix window.
local function make_type_hierarchy_handler()
@@ -603,17 +537,19 @@ local function make_type_hierarchy_handler()
end
end
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls
-M[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler()
+RCS[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler()
+--- @deprecated remove in 0.13
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls
-M[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler()
+RCS[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler()
--- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
---- @param result lsp.LogMessageParams
-M[ms.window_logMessage] = function(_, result, ctx, _)
- local message_type = result.type
- local message = result.message
+--- @param params lsp.LogMessageParams
+NSC['window/logMessage'] = function(_, params, ctx)
+ local message_type = params.type
+ local message = params.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
@@ -629,14 +565,14 @@ M[ms.window_logMessage] = function(_, result, ctx, _)
else
log.debug(message)
end
- return result
+ return params
end
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
---- @param result lsp.ShowMessageParams
-M[ms.window_showMessage] = function(_, result, ctx, _)
- local message_type = result.type
- local message = result.message
+--- @param params lsp.ShowMessageParams
+NSC['window/showMessage'] = function(_, params, ctx)
+ local message_type = params.type
+ local message = params.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
@@ -650,15 +586,16 @@ M[ms.window_showMessage] = function(_, result, ctx, _)
local message_type_name = protocol.MessageType[message_type]
api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message))
end
- return result
+ return params
end
+--- @private
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
---- @param result lsp.ShowDocumentParams
-M[ms.window_showDocument] = function(_, result, ctx, _)
- local uri = result.uri
+--- @param params lsp.ShowDocumentParams
+RSC[ms.window_showDocument] = function(_, params, ctx)
+ local uri = params.uri
- if result.external then
+ if params.external then
-- TODO(lvimuser): ask the user for confirmation
local cmd, err = vim.ui.open(uri)
local ret = cmd and cmd:wait(2000) or nil
@@ -686,35 +623,39 @@ M[ms.window_showDocument] = function(_, result, ctx, _)
local location = {
uri = uri,
- range = result.selection,
+ range = params.selection,
}
local success = util.show_document(location, client.offset_encoding, {
reuse_win = true,
- focus = result.takeFocus,
+ focus = params.takeFocus,
})
return { success = success or false }
end
---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
-M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config)
- return vim.lsp.inlay_hint.on_refresh(err, result, ctx, config)
+RSC[ms.workspace_inlayHint_refresh] = function(err, result, ctx)
+ return vim.lsp.inlay_hint.on_refresh(err, result, ctx)
end
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
-M[ms.workspace_semanticTokens_refresh] = function(err, result, ctx, _config)
+RSC[ms.workspace_semanticTokens_refresh] = function(err, result, ctx)
return vim.lsp.semantic_tokens._refresh(err, result, ctx)
end
+--- @nodoc
+--- @type table<string, lsp.Handler>
+M = vim.tbl_extend('force', M, RSC, NSC, RCS)
+
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
+ --- @diagnostic disable-next-line:redundant-parameter
M[k] = function(err, result, ctx, config)
if log.trace() then
log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
- config = config,
})
end
@@ -735,6 +676,7 @@ for k, fn in pairs(M) do
return
end
+ --- @diagnostic disable-next-line:redundant-parameter
return fn(err, result, ctx, config)
end
end
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 18066a84db..0d314108fe 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -39,12 +39,27 @@ local function check_active_clients()
elseif type(client.config.cmd) == 'function' then
cmd = tostring(client.config.cmd)
end
+ local dirs_info ---@type string
+ if client.workspace_folders and #client.workspace_folders > 1 then
+ dirs_info = string.format(
+ ' Workspace folders:\n %s',
+ vim
+ .iter(client.workspace_folders)
+ ---@param folder lsp.WorkspaceFolder
+ :map(function(folder)
+ return folder.name
+ end)
+ :join('\n ')
+ )
+ else
+ dirs_info = string.format(
+ ' Root directory: %s',
+ client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~')
+ ) or nil
+ end
report_info(table.concat({
string.format('%s (id: %d)', client.name, client.id),
- string.format(
- ' Root directory: %s',
- client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~') or nil
- ),
+ dirs_info,
string.format(' Command: %s', cmd),
string.format(' Settings: %s', vim.inspect(client.settings, { newline = '\n ' })),
string.format(
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index 61059180fe..f1ae9a8e9e 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -37,7 +37,7 @@ local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
---@param result lsp.InlayHint[]?
---@param ctx lsp.HandlerContext
---@private
-function M.on_inlayhint(err, result, ctx, _)
+function M.on_inlayhint(err, result, ctx)
if err then
log.error('inlayhint', err)
return
@@ -65,37 +65,29 @@ function M.on_inlayhint(err, result, ctx, _)
if num_unprocessed == 0 then
client_hints[client_id] = {}
bufstate.version = ctx.version
- api.nvim__redraw({ buf = bufnr, valid = true })
+ api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
return
end
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
- ---@param position lsp.Position
- ---@return integer
- local function pos_to_byte(position)
- local col = position.character
- if col > 0 then
- local line = lines[position.line + 1] or ''
- return util._str_byteindex_enc(line, col, client.offset_encoding)
- end
- return col
- end
for _, hint in ipairs(result) do
local lnum = hint.position.line
- hint.position.character = pos_to_byte(hint.position)
+ local line = lines and lines[lnum + 1] or ''
+ hint.position.character =
+ vim.str_byteindex(line, client.offset_encoding, hint.position.character, false)
table.insert(new_lnum_hints[lnum], hint)
end
client_hints[client_id] = new_lnum_hints
bufstate.version = ctx.version
- api.nvim__redraw({ buf = bufnr, valid = true })
+ api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
end
--- |lsp-handler| for the method `workspace/inlayHint/refresh`
---@param ctx lsp.HandlerContext
---@private
-function M.on_refresh(err, _, ctx, _)
+function M.on_refresh(err, _, ctx)
if err then
return vim.NIL
end
@@ -145,7 +137,7 @@ end
--- @return vim.lsp.inlay_hint.get.ret[]
--- @since 12
function M.get(filter)
- vim.validate({ filter = { filter, 'table', true } })
+ vim.validate('filter', filter, 'table', true)
filter = filter or {}
local bufnr = filter.bufnr
@@ -223,7 +215,7 @@ local function clear(bufnr)
end
end
api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
- api.nvim__redraw({ buf = bufnr, valid = true })
+ api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
end
--- Disable inlay hints for a buffer
@@ -375,11 +367,11 @@ api.nvim_set_decoration_provider(namespace, {
--- @return boolean
--- @since 12
function M.is_enabled(filter)
- vim.validate({ filter = { filter, 'table', true } })
+ vim.validate('filter', filter, 'table', true)
filter = filter or {}
local bufnr = filter.bufnr
- vim.validate({ bufnr = { bufnr, 'number', true } })
+ vim.validate('bufnr', bufnr, 'number', true)
if bufnr == nil then
return globalstate.enabled
elseif bufnr == 0 then
@@ -406,7 +398,8 @@ end
--- @param filter vim.lsp.inlay_hint.enable.Filter?
--- @since 12
function M.enable(enable, filter)
- vim.validate({ enable = { enable, 'boolean', true }, filter = { filter, 'table', true } })
+ vim.validate('enable', enable, 'boolean', true)
+ vim.validate('filter', filter, 'table', true)
enable = enable == nil or enable
filter = filter or {}
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 4f177b47fd..ec78dd3dc5 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -32,12 +32,12 @@ local function notify(msg, level)
end
end
-local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'lsp.log')
+local logfilename = vim.fs.joinpath(vim.fn.stdpath('log') --[[@as string]], 'lsp.log')
-- TODO: Ideally the directory should be created in open_logfile(), right
-- before opening the log file, but open_logfile() can be called from libuv
-- callbacks, where using fn.mkdir() is not allowed.
-vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+vim.fn.mkdir(vim.fn.stdpath('log') --[[@as string]], 'p')
--- Returns the log filename.
---@return string log filename
@@ -82,6 +82,7 @@ end
for level, levelnr in pairs(log_levels) do
-- Also export the log level on the root object.
+ ---@diagnostic disable-next-line: no-unknown
log[level] = levelnr
-- Add a reverse lookup.
@@ -93,7 +94,7 @@ end
--- @return fun(...:any): boolean?
local function create_logger(level, levelnr)
return function(...)
- if levelnr < current_log_level then
+ if not log.should_log(levelnr) then
return false
end
local argc = select('#', ...)
@@ -169,7 +170,7 @@ end
--- Checks whether the level is sufficient for logging.
---@param level integer log level
----@return bool : true if would log, false if not
+---@return boolean : true if would log, false if not
function log.should_log(level)
return level >= current_log_level
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 1699fff0c1..7db48b0c06 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -12,6 +12,8 @@ end
local sysname = vim.uv.os_uname().sysname
+--- @class vim.lsp.protocol.constants
+--- @nodoc
local constants = {
--- @enum lsp.DiagnosticSeverity
DiagnosticSeverity = {
@@ -314,7 +316,9 @@ local constants = {
},
}
--- Protocol for the Microsoft Language Server Protocol (mslsp)
+--- Protocol for the Microsoft Language Server Protocol (mslsp)
+--- @class vim.lsp.protocol : vim.lsp.protocol.constants
+--- @nodoc
local protocol = {}
--- @diagnostic disable:no-unknown
@@ -334,7 +338,9 @@ function protocol.make_client_capabilities()
return {
general = {
positionEncodings = {
+ 'utf-8',
'utf-16',
+ 'utf-32',
},
},
textDocument = {
@@ -615,9 +621,109 @@ function protocol.resolve_capabilities(server_capabilities)
end
-- Generated by gen_lsp.lua, keep at end of file.
+--- @alias vim.lsp.protocol.Method.ClientToServer
+--- | 'callHierarchy/incomingCalls',
+--- | 'callHierarchy/outgoingCalls',
+--- | 'codeAction/resolve',
+--- | 'codeLens/resolve',
+--- | 'completionItem/resolve',
+--- | 'documentLink/resolve',
+--- | '$/setTrace',
+--- | 'exit',
+--- | 'initialize',
+--- | 'initialized',
+--- | 'inlayHint/resolve',
+--- | 'notebookDocument/didChange',
+--- | 'notebookDocument/didClose',
+--- | 'notebookDocument/didOpen',
+--- | 'notebookDocument/didSave',
+--- | 'shutdown',
+--- | 'textDocument/codeAction',
+--- | 'textDocument/codeLens',
+--- | 'textDocument/colorPresentation',
+--- | 'textDocument/completion',
+--- | 'textDocument/declaration',
+--- | 'textDocument/definition',
+--- | 'textDocument/diagnostic',
+--- | 'textDocument/didChange',
+--- | 'textDocument/didClose',
+--- | 'textDocument/didOpen',
+--- | 'textDocument/didSave',
+--- | 'textDocument/documentColor',
+--- | 'textDocument/documentHighlight',
+--- | 'textDocument/documentLink',
+--- | 'textDocument/documentSymbol',
+--- | 'textDocument/foldingRange',
+--- | 'textDocument/formatting',
+--- | 'textDocument/hover',
+--- | 'textDocument/implementation',
+--- | 'textDocument/inlayHint',
+--- | 'textDocument/inlineCompletion',
+--- | 'textDocument/inlineValue',
+--- | 'textDocument/linkedEditingRange',
+--- | 'textDocument/moniker',
+--- | 'textDocument/onTypeFormatting',
+--- | 'textDocument/prepareCallHierarchy',
+--- | 'textDocument/prepareRename',
+--- | 'textDocument/prepareTypeHierarchy',
+--- | 'textDocument/rangeFormatting',
+--- | 'textDocument/rangesFormatting',
+--- | 'textDocument/references',
+--- | 'textDocument/rename',
+--- | 'textDocument/selectionRange',
+--- | 'textDocument/semanticTokens/full',
+--- | 'textDocument/semanticTokens/full/delta',
+--- | 'textDocument/semanticTokens/range',
+--- | 'textDocument/signatureHelp',
+--- | 'textDocument/typeDefinition',
+--- | 'textDocument/willSave',
+--- | 'textDocument/willSaveWaitUntil',
+--- | 'typeHierarchy/subtypes',
+--- | 'typeHierarchy/supertypes',
+--- | 'window/workDoneProgress/cancel',
+--- | 'workspaceSymbol/resolve',
+--- | 'workspace/diagnostic',
+--- | 'workspace/didChangeConfiguration',
+--- | 'workspace/didChangeWatchedFiles',
+--- | 'workspace/didChangeWorkspaceFolders',
+--- | 'workspace/didCreateFiles',
+--- | 'workspace/didDeleteFiles',
+--- | 'workspace/didRenameFiles',
+--- | 'workspace/executeCommand',
+--- | 'workspace/symbol',
+--- | 'workspace/willCreateFiles',
+--- | 'workspace/willDeleteFiles',
+--- | 'workspace/willRenameFiles',
+
+--- @alias vim.lsp.protocol.Method.ServerToClient
+--- | 'client/registerCapability',
+--- | 'client/unregisterCapability',
+--- | '$/logTrace',
+--- | 'telemetry/event',
+--- | 'textDocument/publishDiagnostics',
+--- | 'window/logMessage',
+--- | 'window/showDocument',
+--- | 'window/showMessage',
+--- | 'window/showMessageRequest',
+--- | 'window/workDoneProgress/create',
+--- | 'workspace/applyEdit',
+--- | 'workspace/codeLens/refresh',
+--- | 'workspace/configuration',
+--- | 'workspace/diagnostic/refresh',
+--- | 'workspace/foldingRange/refresh',
+--- | 'workspace/inlayHint/refresh',
+--- | 'workspace/inlineValue/refresh',
+--- | 'workspace/semanticTokens/refresh',
+--- | 'workspace/workspaceFolders',
+
+--- @alias vim.lsp.protocol.Method
+--- | vim.lsp.protocol.Method.ClientToServer
+--- | vim.lsp.protocol.Method.ServerToClient
+
+-- Generated by gen_lsp.lua, keep at end of file.
---
----@enum vim.lsp.protocol.Methods
----@see https://microsoft.github.io/language-server-protocol/specification/#metaModel
+--- @enum vim.lsp.protocol.Methods
+--- @see https://microsoft.github.io/language-server-protocol/specification/#metaModel
--- LSP method names.
protocol.Methods = {
--- A request to resolve the incoming calls for a given `CallHierarchyItem`.
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index e79dbd2db3..6c8564845f 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -1,7 +1,7 @@
local uv = vim.uv
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
-local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
+local validate, schedule_wrap = vim.validate, vim.schedule_wrap
local is_win = vim.fn.has('win32') == 1
@@ -152,9 +152,7 @@ end
---@param err table The error object
---@return string error_message The formatted error message
function M.format_rpc_error(err)
- validate({
- err = { err, 't' },
- })
+ validate('err', err, 'table')
-- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number.
@@ -329,10 +327,8 @@ end
---@return boolean success `true` if request could be sent, `false` if not
---@return integer? message_id if request could be sent, `nil` if not
function Client:request(method, params, callback, notify_reply_callback)
- validate({
- callback = { callback, 'f' },
- notify_reply_callback = { notify_reply_callback, 'f', true },
- })
+ validate('callback', callback, 'function')
+ validate('notify_reply_callback', notify_reply_callback, 'function', true)
self.message_index = self.message_index + 1
local message_id = self.message_index
local result = self:encode_and_send({
@@ -413,49 +409,44 @@ function Client:handle_body(body)
local err --- @type lsp.ResponseError|nil
-- Schedule here so that the users functions don't trigger an error and
-- we can still use the result.
- schedule(function()
- coroutine.wrap(function()
- local status, result
- status, result, err = self:try_call(
- M.client_errors.SERVER_REQUEST_HANDLER_ERROR,
- self.dispatchers.server_request,
- decoded.method,
- decoded.params
- )
- log.debug(
- 'server_request: callback result',
- { status = status, result = result, err = err }
- )
- if status then
- if result == nil and err == nil then
- error(
- string.format(
- 'method %q: either a result or an error must be sent to the server in response',
- decoded.method
- )
- )
- end
- if err then
- ---@cast err lsp.ResponseError
- assert(
- type(err) == 'table',
- 'err must be a table. Use rpc_response_error to help format errors.'
- )
- ---@type string
- local code_name = assert(
- protocol.ErrorCodes[err.code],
- 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
+ vim.schedule(coroutine.wrap(function()
+ local status, result
+ status, result, err = self:try_call(
+ M.client_errors.SERVER_REQUEST_HANDLER_ERROR,
+ self.dispatchers.server_request,
+ decoded.method,
+ decoded.params
+ )
+ log.debug('server_request: callback result', { status = status, result = result, err = err })
+ if status then
+ if result == nil and err == nil then
+ error(
+ string.format(
+ 'method %q: either a result or an error must be sent to the server in response',
+ decoded.method
)
- err.message = err.message or code_name
- end
- else
- -- On an exception, result will contain the error message.
- err = M.rpc_response_error(protocol.ErrorCodes.InternalError, result)
- result = nil
+ )
+ end
+ if err then
+ ---@cast err lsp.ResponseError
+ assert(
+ type(err) == 'table',
+ 'err must be a table. Use rpc_response_error to help format errors.'
+ )
+ ---@type string
+ local code_name = assert(
+ protocol.ErrorCodes[err.code],
+ 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
+ )
+ err.message = err.message or code_name
end
- self:send_response(decoded.id, err, result)
- end)()
- end)
+ else
+ -- On an exception, result will contain the error message.
+ err = M.rpc_response_error(protocol.ErrorCodes.InternalError, result)
+ result = nil
+ end
+ self:send_response(decoded.id, err, result)
+ end))
-- This works because we are expecting vim.NIL here
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- We sent a number, so we expect a number.
@@ -465,9 +456,7 @@ function Client:handle_body(body)
local notify_reply_callbacks = self.notify_reply_callbacks
local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
if notify_reply_callback then
- validate({
- notify_reply_callback = { notify_reply_callback, 'f' },
- })
+ validate('notify_reply_callback', notify_reply_callback, 'function')
notify_reply_callback(result_id)
notify_reply_callbacks[result_id] = nil
end
@@ -498,9 +487,7 @@ function Client:handle_body(body)
local callback = message_callbacks and message_callbacks[result_id]
if callback then
message_callbacks[result_id] = nil
- validate({
- callback = { callback, 'f' },
- })
+ validate('callback', callback, 'function')
if decoded.error then
decoded.error = setmetatable(decoded.error, {
__tostring = M.format_rpc_error,
@@ -734,10 +721,8 @@ end
function M.start(cmd, dispatchers, extra_spawn_params)
log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params })
- validate({
- cmd = { cmd, 't' },
- dispatchers = { dispatchers, 't', true },
- })
+ validate('cmd', cmd, 'table')
+ validate('dispatchers', dispatchers, 'table', true)
extra_spawn_params = extra_spawn_params or {}
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 8182457dd0..215e5f41aa 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -99,11 +99,12 @@ local function tokens_to_ranges(data, bufnr, client, request)
local legend = client.server_capabilities.semanticTokensProvider.legend
local token_types = legend.tokenTypes
local token_modifiers = legend.tokenModifiers
+ local encoding = client.offset_encoding
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
local ranges = {} ---@type STTokenRange[]
local start = uv.hrtime()
- local ms_to_ns = 1000 * 1000
+ local ms_to_ns = 1e6
local yield_interval_ns = 5 * ms_to_ns
local co, is_main = coroutine.running()
@@ -135,20 +136,13 @@ local function tokens_to_ranges(data, bufnr, client, request)
-- data[i+3] +1 because Lua tables are 1-indexed
local token_type = token_types[data[i + 3] + 1]
- local modifiers = modifiers_from_number(data[i + 4], token_modifiers)
-
- local function _get_byte_pos(col)
- if col > 0 then
- local buf_line = lines[line + 1] or ''
- return util._str_byteindex_enc(buf_line, col, client.offset_encoding)
- end
- return col
- end
-
- local start_col = _get_byte_pos(start_char)
- local end_col = _get_byte_pos(start_char + data[i + 2])
if token_type then
+ local modifiers = modifiers_from_number(data[i + 4], token_modifiers)
+ local end_char = start_char + data[i + 2]
+ local buf_line = lines and lines[line + 1] or ''
+ local start_col = vim.str_byteindex(buf_line, encoding, start_char, false)
+ local end_col = vim.str_byteindex(buf_line, encoding, end_char, false)
ranges[#ranges + 1] = {
line = line,
start_col = start_col,
@@ -386,6 +380,37 @@ function STHighlighter:process_response(response, client, version)
api.nvim__redraw({ buf = self.bufnr, valid = true })
end
+--- @param bufnr integer
+--- @param ns integer
+--- @param token STTokenRange
+--- @param hl_group string
+--- @param priority integer
+local function set_mark(bufnr, ns, token, hl_group, priority)
+ vim.api.nvim_buf_set_extmark(bufnr, ns, token.line, token.start_col, {
+ hl_group = hl_group,
+ end_col = token.end_col,
+ priority = priority,
+ strict = false,
+ })
+end
+
+--- @param lnum integer
+--- @param foldend integer?
+--- @return boolean, integer?
+local function check_fold(lnum, foldend)
+ if foldend and lnum <= foldend then
+ return true, foldend
+ end
+
+ local folded = vim.fn.foldclosed(lnum)
+
+ if folded == -1 then
+ return false, nil
+ end
+
+ return folded ~= lnum, vim.fn.foldclosedend(lnum)
+end
+
--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|)
---
--- If there is a current result for the buffer and the version matches the
@@ -439,13 +464,14 @@ function STHighlighter:on_win(topline, botline)
-- finishes, clangd sends a refresh request which lets the client
-- re-synchronize the tokens.
- local set_mark = function(token, hl_group, delta)
- vim.api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, {
- hl_group = hl_group,
- end_col = token.end_col,
- priority = vim.highlight.priorities.semantic_tokens + delta,
- strict = false,
- })
+ local function set_mark0(token, hl_group, delta)
+ set_mark(
+ self.bufnr,
+ state.namespace,
+ token,
+ hl_group,
+ vim.hl.priorities.semantic_tokens + delta
+ )
end
local ft = vim.bo[self.bufnr].filetype
@@ -453,13 +479,19 @@ function STHighlighter:on_win(topline, botline)
local first = lower_bound(highlights, topline, 1, #highlights + 1)
local last = upper_bound(highlights, botline, first, #highlights + 1) - 1
+ --- @type boolean?, integer?
+ local is_folded, foldend
+
for i = first, last do
local token = highlights[i]
- if not token.marked then
- set_mark(token, string.format('@lsp.type.%s.%s', token.type, ft), 0)
- for modifier, _ in pairs(token.modifiers) do
- set_mark(token, string.format('@lsp.mod.%s.%s', modifier, ft), 1)
- set_mark(token, string.format('@lsp.typemod.%s.%s.%s', token.type, modifier, ft), 2)
+
+ is_folded, foldend = check_fold(token.line + 1, foldend)
+
+ if not is_folded and not token.marked then
+ set_mark0(token, string.format('@lsp.type.%s.%s', token.type, ft), 0)
+ for modifier in pairs(token.modifiers) do
+ set_mark0(token, string.format('@lsp.mod.%s.%s', modifier, ft), 1)
+ set_mark0(token, string.format('@lsp.typemod.%s.%s.%s', token.type, modifier, ft), 2)
end
token.marked = true
@@ -565,10 +597,8 @@ local M = {}
--- - debounce (integer, default: 200): Debounce token requests
--- to the server by the given number in milliseconds
function M.start(bufnr, client_id, opts)
- vim.validate({
- bufnr = { bufnr, 'n', false },
- client_id = { client_id, 'n', false },
- })
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('client_id', client_id, 'number')
if bufnr == 0 then
bufnr = api.nvim_get_current_buf()
@@ -622,10 +652,8 @@ end
---@param bufnr (integer) Buffer number, or `0` for current buffer
---@param client_id (integer) The ID of the |vim.lsp.Client|
function M.stop(bufnr, client_id)
- vim.validate({
- bufnr = { bufnr, 'n', false },
- client_id = { client_id, 'n', false },
- })
+ vim.validate('bufnr', bufnr, 'number')
+ vim.validate('client_id', client_id, 'number')
if bufnr == 0 then
bufnr = api.nvim_get_current_buf()
@@ -708,9 +736,7 @@ end
---@param bufnr (integer|nil) filter by buffer. All buffers if nil, current
--- buffer if 0
function M.force_refresh(bufnr)
- vim.validate({
- bufnr = { bufnr, 'n', true },
- })
+ vim.validate('bufnr', bufnr, 'number', true)
local buffers = bufnr == nil and vim.tbl_keys(STHighlighter.active)
or bufnr == 0 and { api.nvim_get_current_buf() }
@@ -729,7 +755,7 @@ end
--- @inlinedoc
---
--- Priority for the applied extmark.
---- (Default: `vim.highlight.priorities.semantic_tokens + 3`)
+--- (Default: `vim.hl.priorities.semantic_tokens + 3`)
--- @field priority? integer
--- Highlight a semantic token.
@@ -757,15 +783,9 @@ function M.highlight_token(token, bufnr, client_id, hl_group, opts)
return
end
- opts = opts or {}
- local priority = opts.priority or vim.highlight.priorities.semantic_tokens + 3
+ local priority = opts and opts.priority or vim.hl.priorities.semantic_tokens + 3
- vim.api.nvim_buf_set_extmark(bufnr, state.namespace, token.line, token.start_col, {
- hl_group = hl_group,
- end_col = token.end_col,
- priority = priority,
- strict = false,
- })
+ set_mark(bufnr, state.namespace, token, hl_group, priority)
end
--- |lsp-handler| for the method `workspace/semanticTokens/refresh`
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index bdfe8d51b8..3df45ebff0 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -48,45 +48,6 @@ local str_utfindex = vim.str_utfindex
local str_utf_start = vim.str_utf_start
local str_utf_end = vim.str_utf_end
--- Given a line, byte idx, and offset_encoding convert to the
--- utf-8, utf-16, or utf-32 index.
----@param line string the line to index into
----@param byte integer the byte idx
----@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8)
----@return integer utf_idx for the given encoding
-local function byte_to_utf(line, byte, offset_encoding)
- -- convert to 0 based indexing for str_utfindex
- byte = byte - 1
-
- local utf_idx, _ --- @type integer, integer
- -- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based
- if offset_encoding == 'utf-16' then
- _, utf_idx = str_utfindex(line, byte)
- elseif offset_encoding == 'utf-32' then
- utf_idx, _ = str_utfindex(line, byte)
- else
- utf_idx = byte
- end
-
- -- convert to 1 based indexing
- return utf_idx + 1
-end
-
----@param line string
----@param offset_encoding string
----@return integer
-local function compute_line_length(line, offset_encoding)
- local length, _ --- @type integer, integer
- if offset_encoding == 'utf-16' then
- _, length = str_utfindex(line)
- elseif offset_encoding == 'utf-32' then
- length, _ = str_utfindex(line)
- else
- length = #line
- end
- return length
-end
-
-- Given a line, byte idx, alignment, and offset_encoding convert to the aligned
-- utf-8 index and either the utf-16, or utf-32 index.
---@param line string the line to index into
@@ -101,7 +62,7 @@ local function align_end_position(line, byte, offset_encoding)
char = byte
-- Called in the case of extending an empty line "" -> "a"
elseif byte == #line + 1 then
- char = compute_line_length(line, offset_encoding) + 1
+ char = str_utfindex(line, offset_encoding) + 1
else
-- Modifying line, find the nearest utf codepoint
local offset = str_utf_start(line, byte)
@@ -111,9 +72,10 @@ local function align_end_position(line, byte, offset_encoding)
byte = byte + str_utf_end(line, byte) + 1
end
if byte <= #line then
- char = byte_to_utf(line, byte, offset_encoding)
+ --- Convert to 0 based for input, and from 0 based for output
+ char = str_utfindex(line, offset_encoding, byte - 1) + 1
else
- char = compute_line_length(line, offset_encoding) + 1
+ char = str_utfindex(line, offset_encoding) + 1
end
-- Extending line, find the nearest utf codepoint for the last valid character
end
@@ -153,7 +115,7 @@ local function compute_start_range(
if line then
line_idx = firstline - 1
byte_idx = #line + 1
- char_idx = compute_line_length(line, offset_encoding) + 1
+ char_idx = str_utfindex(line, offset_encoding) + 1
else
line_idx = firstline
byte_idx = 1
@@ -190,10 +152,11 @@ local function compute_start_range(
char_idx = 1
elseif start_byte_idx == #prev_line + 1 then
byte_idx = start_byte_idx
- char_idx = compute_line_length(prev_line, offset_encoding) + 1
+ char_idx = str_utfindex(prev_line, offset_encoding) + 1
else
byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx)
- char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding)
+ --- Convert to 0 based for input, and from 0 based for output
+ char_idx = vim.str_utfindex(prev_line, offset_encoding, byte_idx - 1) + 1
end
-- Return the start difference (shared for new and prev lines)
@@ -230,7 +193,7 @@ local function compute_end_range(
return {
line_idx = lastline - 1,
byte_idx = #prev_line + 1,
- char_idx = compute_line_length(prev_line, offset_encoding) + 1,
+ char_idx = str_utfindex(prev_line, offset_encoding) + 1,
}, { line_idx = 1, byte_idx = 1, char_idx = 1 }
end
-- If firstline == new_lastline, the first change occurred on a line that was deleted.
@@ -376,7 +339,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi
local start_line = lines[start_range.line_idx]
local range_length --- @type integer
if start_line and #start_line > 0 then
- range_length = compute_line_length(start_line, offset_encoding)
+ range_length = str_utfindex(start_line, offset_encoding)
- start_range.char_idx
+ 1
+ line_ending_length
@@ -389,7 +352,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi
for idx = start_range.line_idx + 1, end_range.line_idx - 1 do
-- Length full line plus newline character
if #lines[idx] > 0 then
- range_length = range_length + compute_line_length(lines[idx], offset_encoding) + #line_ending
+ range_length = range_length + str_utfindex(lines[idx], offset_encoding) + #line_ending
else
range_length = range_length + line_ending_length
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 882ec22ca6..6eab0f3da4 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -2,12 +2,8 @@ local protocol = require('vim.lsp.protocol')
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
-local highlight = vim.highlight
local uv = vim.uv
-local npcall = vim.F.npcall
-local split = vim.split
-
local M = {}
local default_border = {
@@ -21,82 +17,73 @@ local default_border = {
{ ' ', 'NormalFloat' },
}
+--- @param border string|(string|[string,string])[]
+local function border_error(border)
+ error(
+ string.format(
+ 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
+ vim.inspect(border)
+ ),
+ 2
+ )
+end
+
+local border_size = {
+ none = { 0, 0 },
+ single = { 2, 2 },
+ double = { 2, 2 },
+ rounded = { 2, 2 },
+ solid = { 2, 2 },
+ shadow = { 1, 1 },
+}
+
--- Check the border given by opts or the default border for the additional
--- size it adds to a float.
----@param opts table optional options for the floating window
---- - border (string or table) the border
----@return table size of border in the form of { height = height, width = width }
+--- @param opts? {border:string|(string|[string,string])[]}
+--- @return integer height
+--- @return integer width
local function get_border_size(opts)
local border = opts and opts.border or default_border
- local height = 0
- local width = 0
if type(border) == 'string' then
- local border_size = {
- none = { 0, 0 },
- single = { 2, 2 },
- double = { 2, 2 },
- rounded = { 2, 2 },
- solid = { 2, 2 },
- shadow = { 1, 1 },
- }
- if border_size[border] == nil then
- error(
- string.format(
- 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
- vim.inspect(border)
- )
- )
+ if not border_size[border] then
+ border_error(border)
end
- height, width = unpack(border_size[border])
- else
- if 8 % #border ~= 0 then
- error(
- string.format(
- 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
- vim.inspect(border)
- )
- )
- end
- local function border_width(id)
- id = (id - 1) % #border + 1
- if type(border[id]) == 'table' then
- -- border specified as a table of <character, highlight group>
- return vim.fn.strdisplaywidth(border[id][1])
- elseif type(border[id]) == 'string' then
- -- border specified as a list of border characters
- return vim.fn.strdisplaywidth(border[id])
- end
- error(
- string.format(
- 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
- vim.inspect(border)
- )
- )
- end
- local function border_height(id)
- id = (id - 1) % #border + 1
- if type(border[id]) == 'table' then
- -- border specified as a table of <character, highlight group>
- return #border[id][1] > 0 and 1 or 0
- elseif type(border[id]) == 'string' then
- -- border specified as a list of border characters
- return #border[id] > 0 and 1 or 0
- end
- error(
- string.format(
- 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
- vim.inspect(border)
- )
- )
+ return unpack(border_size[border])
+ end
+
+ if 8 % #border ~= 0 then
+ border_error(border)
+ end
+
+ --- @param id integer
+ --- @return string
+ local function elem(id)
+ id = (id - 1) % #border + 1
+ local e = border[id]
+ if type(e) == 'table' then
+ -- border specified as a table of <character, highlight group>
+ return e[1]
+ elseif type(e) == 'string' then
+ -- border specified as a list of border characters
+ return e
end
- height = height + border_height(2) -- top
- height = height + border_height(6) -- bottom
- width = width + border_width(4) -- right
- width = width + border_width(8) -- left
+ --- @diagnostic disable-next-line:missing-return
+ border_error(border)
+ end
+
+ --- @param e string
+ local function border_height(e)
+ return #e > 0 and 1 or 0
end
- return { height = height, width = width }
+ local top, bottom = elem(2), elem(6)
+ local height = border_height(top) + border_height(bottom)
+
+ local right, left = elem(4), elem(8)
+ local width = vim.fn.strdisplaywidth(right) + vim.fn.strdisplaywidth(left)
+
+ return height, width
end
--- Splits string at newlines, optionally removing unwanted blank lines.
@@ -122,79 +109,13 @@ local function split_lines(s, no_blank)
end
local function create_window_without_focus()
- local prev = vim.api.nvim_get_current_win()
+ local prev = api.nvim_get_current_win()
vim.cmd.new()
- local new = vim.api.nvim_get_current_win()
- vim.api.nvim_set_current_win(prev)
+ local new = api.nvim_get_current_win()
+ api.nvim_set_current_win(prev)
return new
end
---- Convert byte index to `encoding` index.
---- Convenience wrapper around vim.str_utfindex
----@param line string line to be indexed
----@param index integer|nil byte index (utf-8), or `nil` for length
----@param encoding 'utf-8'|'utf-16'|'utf-32'|nil defaults to utf-16
----@return integer `encoding` index of `index` in `line`
-function M._str_utfindex_enc(line, index, encoding)
- local len32, len16 = vim.str_utfindex(line)
- if not encoding then
- encoding = 'utf-16'
- end
- if encoding == 'utf-8' then
- if index then
- return index
- else
- return #line
- end
- elseif encoding == 'utf-16' then
- if not index or index > len16 then
- return len16
- end
- local _, col16 = vim.str_utfindex(line, index)
- return col16
- elseif encoding == 'utf-32' then
- if not index or index > len32 then
- return len32
- end
- local col32, _ = vim.str_utfindex(line, index)
- return col32
- else
- error('Invalid encoding: ' .. vim.inspect(encoding))
- end
-end
-
---- Convert UTF index to `encoding` index.
---- Convenience wrapper around vim.str_byteindex
----Alternative to vim.str_byteindex that takes an encoding.
----@param line string line to be indexed
----@param index integer UTF index
----@param encoding string utf-8|utf-16|utf-32| defaults to utf-16
----@return integer byte (utf-8) index of `encoding` index `index` in `line`
-function M._str_byteindex_enc(line, index, encoding)
- local len = #line
- if index > len then
- -- LSP spec: if character > line length, default to the line length.
- -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
- return len
- end
- if not encoding then
- encoding = 'utf-16'
- end
- if encoding == 'utf-8' then
- if index then
- return index
- else
- return len
- end
- elseif encoding == 'utf-16' then
- return vim.str_byteindex(line, index, true)
- elseif encoding == 'utf-32' then
- return vim.str_byteindex(line, index)
- else
- error('Invalid encoding: ' .. vim.inspect(encoding))
- end
-end
-
--- Replaces text in a range with new text.
---
--- CAUTION: Changes in-place!
@@ -243,6 +164,8 @@ function M.set_lines(lines, A, B, new_lines)
return lines
end
+--- @param fn fun(x:any):any[]
+--- @return function
local function sort_by_key(fn)
return function(a, b)
local ka, kb = fn(a), fn(b)
@@ -354,7 +277,7 @@ end
--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
---@param position lsp.Position
----@param offset_encoding? string utf-8|utf-16|utf-32
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'
---@return integer
local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- LSP's line and characters are 0-indexed
@@ -364,7 +287,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- character
if col > 0 then
local line = get_line(bufnr, position.line) or ''
- return M._str_byteindex_enc(line, col, offset_encoding or 'utf-16')
+ return vim.str_byteindex(line, offset_encoding, col, false)
end
return col
end
@@ -372,14 +295,13 @@ end
--- Applies a list of text edits to a buffer.
---@param text_edits lsp.TextEdit[]
---@param bufnr integer Buffer id
----@param offset_encoding string utf-8|utf-16|utf-32
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
function M.apply_text_edits(text_edits, bufnr, offset_encoding)
- validate({
- text_edits = { text_edits, 't', false },
- bufnr = { bufnr, 'number', false },
- offset_encoding = { offset_encoding, 'string', false },
- })
+ validate('text_edits', text_edits, 'table', false)
+ validate('bufnr', bufnr, 'number', false)
+ validate('offset_encoding', offset_encoding, 'string', false)
+
if not next(text_edits) then
return
end
@@ -392,10 +314,8 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
vim.bo[bufnr].buflisted = true
-- Fix reversed range and indexing each text_edits
- local index = 0
- --- @param text_edit lsp.TextEdit
- text_edits = vim.tbl_map(function(text_edit)
- index = index + 1
+ for index, text_edit in ipairs(text_edits) do
+ --- @cast text_edit lsp.TextEdit|{_index: integer}
text_edit._index = index
if
@@ -407,8 +327,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
text_edit.range.start = text_edit.range['end']
text_edit.range['end'] = start
end
- return text_edit
- end, text_edits)
+ end
-- Sort text_edits
---@param a lsp.TextEdit | { _index: integer }
@@ -439,47 +358,45 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n')
-- Convert from LSP style ranges to Neovim style ranges.
- local e = {
- start_row = text_edit.range.start.line,
- start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding),
- end_row = text_edit.range['end'].line,
- end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding),
- text = split(text_edit.newText, '\n', { plain = true }),
- }
+ local start_row = text_edit.range.start.line
+ local start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding)
+ local end_row = text_edit.range['end'].line
+ local end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding)
+ local text = vim.split(text_edit.newText, '\n', { plain = true })
local max = api.nvim_buf_line_count(bufnr)
-- If the whole edit is after the lines in the buffer we can simply add the new text to the end
-- of the buffer.
- if max <= e.start_row then
- api.nvim_buf_set_lines(bufnr, max, max, false, e.text)
+ if max <= start_row then
+ api.nvim_buf_set_lines(bufnr, max, max, false, text)
else
- local last_line_len = #(get_line(bufnr, math.min(e.end_row, max - 1)) or '')
+ local last_line_len = #(get_line(bufnr, math.min(end_row, max - 1)) or '')
-- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't
-- accept it so we should fix it here.
- if max <= e.end_row then
- e.end_row = max - 1
- e.end_col = last_line_len
+ if max <= end_row then
+ end_row = max - 1
+ end_col = last_line_len
has_eol_text_edit = true
else
- -- If the replacement is over the end of a line (i.e. e.end_col is equal to the line length and the
+ -- If the replacement is over the end of a line (i.e. end_col is equal to the line length and the
-- replacement text ends with a newline We can likely assume that the replacement is assumed
-- to be meant to replace the newline with another newline and we need to make sure this
-- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r'
-- in the file some servers (clangd on windows) will include that character in the line
-- while nvim_buf_set_text doesn't count it as part of the line.
if
- e.end_col >= last_line_len
- and text_edit.range['end'].character > e.end_col
+ end_col >= last_line_len
+ and text_edit.range['end'].character > end_col
and #text_edit.newText > 0
and string.sub(text_edit.newText, -1) == '\n'
then
- table.remove(e.text, #e.text)
+ table.remove(text, #text)
end
end
- -- Make sure we don't go out of bounds for e.end_col
- e.end_col = math.min(last_line_len, e.end_col)
+ -- Make sure we don't go out of bounds for end_col
+ end_col = math.min(last_line_len, end_col)
- api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
+ api.nvim_buf_set_text(bufnr, start_row, start_col, end_row, end_col, text)
end
end
@@ -495,7 +412,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
-- make sure we don't go out of bounds
pos[1] = math.min(pos[1], max)
pos[2] = math.min(pos[2], #(get_line(bufnr, pos[1] - 1) or ''))
- vim.api.nvim_buf_set_mark(bufnr or 0, mark, pos[1], pos[2], {})
+ api.nvim_buf_set_mark(bufnr or 0, mark, pos[1], pos[2], {})
end
end
@@ -513,7 +430,7 @@ end
---
---@param text_document_edit lsp.TextDocumentEdit
---@param index? integer: Optional index of the edit, if from a list of edits (or nil, if not from a list)
----@param offset_encoding? string
+---@param offset_encoding? 'utf-8'|'utf-16'|'utf-32'
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
local text_document = text_document_edit.textDocument
@@ -523,19 +440,15 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
'apply_text_document_edit must be called with valid offset encoding',
vim.log.levels.WARN
)
- end
-
- -- For lists of text document edits,
- -- do not check the version after the first edit.
- local should_check_version = true
- if index and index > 1 then
- should_check_version = false
+ return
end
-- `VersionedTextDocumentIdentifier`s version may be null
-- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
if
- should_check_version
+ -- For lists of text document edits,
+ -- do not check the version after the first edit.
+ not (index and index > 1)
and (
text_document.version
and text_document.version > 0
@@ -553,6 +466,9 @@ local function path_components(path)
return vim.split(path, '/', { plain = true })
end
+--- @param path string[]
+--- @param prefix string[]
+--- @return boolean
local function path_under_prefix(path, prefix)
for i, c in ipairs(prefix) do
if c ~= path[i] then
@@ -562,17 +478,24 @@ local function path_under_prefix(path, prefix)
return true
end
---- Get list of buffers whose filename matches the given path prefix (normalized full path)
+--- Get list of loaded writable buffers whose filename matches the given path
+--- prefix (normalized full path).
---@param prefix string
---@return integer[]
-local function get_bufs_with_prefix(prefix)
- prefix = path_components(prefix)
- local buffers = {}
- for _, v in ipairs(vim.api.nvim_list_bufs()) do
- local bname = vim.api.nvim_buf_get_name(v)
- local path = path_components(vim.fs.normalize(bname, { expand_env = false }))
- if path_under_prefix(path, prefix) then
- table.insert(buffers, v)
+local function get_writable_bufs(prefix)
+ local prefix_parts = path_components(prefix)
+ local buffers = {} --- @type integer[]
+ for _, buf in ipairs(api.nvim_list_bufs()) do
+ -- No need to care about unloaded or nofile buffers. Also :saveas won't work for them.
+ if
+ api.nvim_buf_is_loaded(buf)
+ and not vim.list_contains({ 'nofile', 'nowrite' }, vim.bo[buf].buftype)
+ then
+ local bname = api.nvim_buf_get_name(buf)
+ local path = path_components(vim.fs.normalize(bname, { expand_env = false }))
+ if path_under_prefix(path, prefix_parts) then
+ buffers[#buffers + 1] = buf
+ end
end
end
return buffers
@@ -616,19 +539,13 @@ function M.rename(old_fname, new_fname, opts)
local buf_rename = {} ---@type table<integer, {from: string, to: string}>
local old_fname_pat = '^' .. vim.pesc(old_fname_full)
- for b in
- vim.iter(get_bufs_with_prefix(old_fname_full)):filter(function(b)
- -- No need to care about unloaded or nofile buffers. Also :saveas won't work for them.
- return api.nvim_buf_is_loaded(b)
- and not vim.list_contains({ 'nofile', 'nowrite' }, vim.bo[b].buftype)
- end)
- do
+ for _, b in ipairs(get_writable_bufs(old_fname_full)) do
-- Renaming a buffer may conflict with another buffer that happens to have the same name. In
-- most cases, this would have been already detected by the file conflict check above, but the
-- conflicting buffer may not be associated with a file. For example, 'buftype' can be "nofile"
-- or "nowrite", or the buffer can be a normal buffer but has not been written to the file yet.
-- Renaming should fail in such cases to avoid losing the contents of the conflicting buffer.
- local old_bname = vim.api.nvim_buf_get_name(b)
+ local old_bname = api.nvim_buf_get_name(b)
local new_bname = old_bname:gsub(old_fname_pat, escape_gsub_repl(new_fname))
if vim.fn.bufexists(new_bname) == 1 then
local existing_buf = vim.fn.bufnr(new_bname)
@@ -702,7 +619,7 @@ end
--- Applies a `WorkspaceEdit`.
---
---@param workspace_edit lsp.WorkspaceEdit
----@param offset_encoding string utf-8|utf-16|utf-32 (required)
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32' (required)
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
function M.apply_workspace_edit(workspace_edit, offset_encoding)
if offset_encoding == nil then
@@ -710,16 +627,18 @@ function M.apply_workspace_edit(workspace_edit, offset_encoding)
'apply_workspace_edit must be called with valid offset encoding',
vim.log.levels.WARN
)
+ return
end
if workspace_edit.documentChanges then
for idx, change in ipairs(workspace_edit.documentChanges) do
if change.kind == 'rename' then
- M.rename(vim.uri_to_fname(change.oldUri), vim.uri_to_fname(change.newUri), change.options)
+ local options = change.options --[[@as vim.lsp.util.rename.Opts]]
+ M.rename(vim.uri_to_fname(change.oldUri), vim.uri_to_fname(change.newUri), options)
elseif change.kind == 'create' then
create_file(change)
elseif change.kind == 'delete' then
delete_file(change)
- elseif change.kind then
+ elseif change.kind then --- @diagnostic disable-line:undefined-field
error(string.format('Unsupported change: %q', vim.inspect(change)))
else
M.apply_text_document_edit(change, idx, offset_encoding)
@@ -748,7 +667,7 @@ end
--- then the corresponding value is returned without further modifications.
---
---@param input lsp.MarkedString|lsp.MarkedString[]|lsp.MarkupContent
----@param contents string[]|nil List of strings to extend with converted lines. Defaults to {}.
+---@param contents string[]? List of strings to extend with converted lines. Defaults to {}.
---@return string[] extended with lines of converted markdown.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
function M.convert_input_to_markdown_lines(input, contents)
@@ -781,111 +700,117 @@ function M.convert_input_to_markdown_lines(input, contents)
return contents
end
+--- Returns the line/column-based position in `contents` at the given offset.
+---
+---@param offset integer
+---@param contents string[]
+---@return { [1]: integer, [2]: integer }?
+local function get_pos_from_offset(offset, contents)
+ local i = 0
+ for l, line in ipairs(contents) do
+ if offset >= i and offset < i + #line then
+ return { l - 1, offset - i + 1 }
+ else
+ i = i + #line + 1
+ end
+ end
+end
+
--- Converts `textDocument/signatureHelp` response to markdown lines.
---
---@param signature_help lsp.SignatureHelp Response of `textDocument/SignatureHelp`
----@param ft string|nil filetype that will be use as the `lang` for the label markdown code block
----@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets
----@return string[]|nil table list of lines of converted markdown.
----@return number[]|nil table of active hl
+---@param ft string? filetype that will be use as the `lang` for the label markdown code block
+---@param triggers string[]? list of trigger characters from the lsp server. used to better determine parameter offsets
+---@return string[]? # lines of converted markdown.
+---@return Range4? # highlight range for the active parameter
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers)
- if not signature_help.signatures then
- return
- end
--The active signature. If omitted or the value lies outside the range of
--`signatures` the value defaults to zero or is ignored if `signatures.length == 0`.
--Whenever possible implementors should make an active decision about
--the active signature and shouldn't rely on a default value.
- local contents = {}
- local active_hl
+ local contents = {} --- @type string[]
+ local active_offset ---@type [integer, integer]?
local active_signature = signature_help.activeSignature or 0
-- If the activeSignature is not inside the valid range, then clip it.
-- In 3.15 of the protocol, activeSignature was allowed to be negative
if active_signature >= #signature_help.signatures or active_signature < 0 then
active_signature = 0
end
- local signature = signature_help.signatures[active_signature + 1]
- if not signature then
- return
- end
+ local signature = vim.deepcopy(signature_help.signatures[active_signature + 1])
local label = signature.label
if ft then
-- wrap inside a code block for proper rendering
label = ('```%s\n%s\n```'):format(ft, label)
end
- list_extend(contents, split(label, '\n', { plain = true, trimempty = true }))
- if signature.documentation then
+ list_extend(contents, vim.split(label, '\n', { plain = true, trimempty = true }))
+ local doc = signature.documentation
+ if doc then
-- if LSP returns plain string, we treat it as plaintext. This avoids
-- special characters like underscore or similar from being interpreted
-- as markdown font modifiers
- if type(signature.documentation) == 'string' then
- signature.documentation = { kind = 'plaintext', value = signature.documentation }
+ if type(doc) == 'string' then
+ signature.documentation = { kind = 'plaintext', value = doc }
end
M.convert_input_to_markdown_lines(signature.documentation, contents)
end
if signature.parameters and #signature.parameters > 0 then
- local active_parameter = (signature.activeParameter or signature_help.activeParameter or 0)
- if active_parameter < 0 then
- active_parameter = 0
- end
+ -- First check if the signature has an activeParameter. If it doesn't check if the response
+ -- had that property instead. Else just default to 0.
+ local active_parameter =
+ math.max(signature.activeParameter or signature_help.activeParameter or 0, 0)
-- If the activeParameter is > #parameters, then set it to the last
-- NOTE: this is not fully according to the spec, but a client-side interpretation
- if active_parameter >= #signature.parameters then
- active_parameter = #signature.parameters - 1
- end
+ active_parameter = math.min(active_parameter, #signature.parameters - 1)
local parameter = signature.parameters[active_parameter + 1]
- if parameter then
- --[=[
- --Represents a parameter of a callable-signature. A parameter can
- --have a label and a doc-comment.
- interface ParameterInformation {
- --The label of this parameter information.
- --
- --Either a string or an inclusive start and exclusive end offsets within its containing
- --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
- --string representation as `Position` and `Range` does.
- --
- --*Note*: a label of type string should be a substring of its containing signature label.
- --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
- label: string | [number, number];
- --The human-readable doc-comment of this parameter. Will be shown
- --in the UI but can be omitted.
- documentation?: string | MarkupContent;
- }
- --]=]
- if parameter.label then
- if type(parameter.label) == 'table' then
- active_hl = parameter.label
- else
- local offset = 1
- -- try to set the initial offset to the first found trigger character
- for _, t in ipairs(triggers or {}) do
- local trigger_offset = signature.label:find(t, 1, true)
- if trigger_offset and (offset == 1 or trigger_offset < offset) then
- offset = trigger_offset
- end
- end
- for p, param in pairs(signature.parameters) do
- offset = signature.label:find(param.label, offset, true)
- if not offset then
- break
- end
- if p == active_parameter + 1 then
- active_hl = { offset - 1, offset + #parameter.label - 1 }
- break
- end
- offset = offset + #param.label + 1
- end
+ local parameter_label = parameter.label
+ if type(parameter_label) == 'table' then
+ active_offset = parameter_label
+ else
+ local offset = 1 ---@type integer?
+ -- try to set the initial offset to the first found trigger character
+ for _, t in ipairs(triggers or {}) do
+ local trigger_offset = signature.label:find(t, 1, true)
+ if trigger_offset and (offset == 1 or trigger_offset < offset) then
+ offset = trigger_offset
end
end
- if parameter.documentation then
- M.convert_input_to_markdown_lines(parameter.documentation, contents)
+ for p, param in pairs(signature.parameters) do
+ local plabel = param.label
+ assert(type(plabel) == 'string', 'Expected label to be a string')
+ offset = signature.label:find(plabel, offset, true)
+ if not offset then
+ break
+ end
+ if p == active_parameter + 1 then
+ active_offset = { offset - 1, offset + #parameter_label - 1 }
+ break
+ end
+ offset = offset + #param.label + 1
end
end
+ if parameter.documentation then
+ M.convert_input_to_markdown_lines(parameter.documentation, contents)
+ end
end
+
+ local active_hl = nil
+ if active_offset then
+ -- Account for the start of the markdown block.
+ if ft then
+ active_offset[1] = active_offset[1] + #contents[1]
+ active_offset[2] = active_offset[2] + #contents[1]
+ end
+
+ local a_start = get_pos_from_offset(active_offset[1], contents)
+ local a_end = get_pos_from_offset(active_offset[2], contents)
+ if a_start and a_end then
+ active_hl = { a_start[1], a_start[2], a_end[1], a_end[2] }
+ end
+ end
+
return contents, active_hl
end
@@ -894,32 +819,15 @@ end
---
---@param width integer window width (in character cells)
---@param height integer window height (in character cells)
----@param opts table optional
---- - offset_x (integer) offset to add to `col`
---- - offset_y (integer) offset to add to `row`
---- - border (string or table) override `border`
---- - focusable (string or table) override `focusable`
---- - zindex (string or table) override `zindex`, defaults to 50
---- - relative ("mouse"|"cursor") defaults to "cursor"
---- - anchor_bias ("auto"|"above"|"below") defaults to "auto"
---- - "auto": place window based on which side of the cursor has more lines
---- - "above": place the window above the cursor unless there are not enough lines
---- to display the full window height.
---- - "below": place the window below the cursor unless there are not enough lines
---- to display the full window height.
----@return table Options
+---@param opts? vim.lsp.util.open_floating_preview.Opts
+---@return vim.api.keyset.win_config
function M.make_floating_popup_options(width, height, opts)
- validate({
- opts = { opts, 't', true },
- })
+ validate('opts', opts, 'table', true)
opts = opts or {}
- validate({
- ['opts.offset_x'] = { opts.offset_x, 'n', true },
- ['opts.offset_y'] = { opts.offset_y, 'n', true },
- })
+ validate('opts.offset_x', opts.offset_x, 'number', true)
+ validate('opts.offset_y', opts.offset_y, 'number', true)
local anchor = ''
- local row, col
local lines_above = opts.relative == 'mouse' and vim.fn.getmousepos().line - 1
or vim.fn.winline() - 1
@@ -927,7 +835,7 @@ function M.make_floating_popup_options(width, height, opts)
local anchor_bias = opts.anchor_bias or 'auto'
- local anchor_below
+ local anchor_below --- @type boolean?
if anchor_bias == 'below' then
anchor_below = (lines_below > lines_above) or (height <= lines_below)
@@ -938,7 +846,8 @@ function M.make_floating_popup_options(width, height, opts)
anchor_below = lines_below > lines_above
end
- local border_height = get_border_size(opts).height
+ local border_height = get_border_size(opts)
+ local row, col --- @type integer?, integer?
if anchor_below then
anchor = anchor .. 'N'
height = math.max(math.min(lines_below - border_height, height), 0)
@@ -960,7 +869,7 @@ function M.make_floating_popup_options(width, height, opts)
end
local title = (opts.border and opts.title) and opts.title or nil
- local title_pos
+ local title_pos --- @type 'left'|'center'|'right'?
if title then
title_pos = opts.title_pos or 'center'
@@ -982,13 +891,21 @@ function M.make_floating_popup_options(width, height, opts)
}
end
+--- @class vim.lsp.util.show_document.Opts
+--- @inlinedoc
+---
+--- Jump to existing window if buffer is already open.
+--- @field reuse_win? boolean
+---
+--- Whether to focus/jump to location if possible.
+--- (defaults: true)
+--- @field focus? boolean
+
--- Shows document and optionally jumps to the location.
---
---@param location lsp.Location|lsp.LocationLink
----@param offset_encoding string|nil utf-8|utf-16|utf-32
----@param opts table|nil options
---- - reuse_win (boolean) Jump to existing window if buffer is already open.
---- - focus (boolean) Whether to focus/jump to location if possible. Defaults to true.
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'?
+---@param opts? vim.lsp.util.show_document.Opts
---@return boolean `true` if succeeded
function M.show_document(location, offset_encoding, opts)
-- location may be Location or LocationLink
@@ -998,6 +915,7 @@ function M.show_document(location, offset_encoding, opts)
end
if offset_encoding == nil then
vim.notify_once('show_document must be called with valid offset encoding', vim.log.levels.WARN)
+ return false
end
local bufnr = vim.uri_to_bufnr(uri)
@@ -1041,18 +959,13 @@ end
--- Jumps to a location.
---
+---@deprecated use `vim.lsp.util.show_document` with `{focus=true}` instead
---@param location lsp.Location|lsp.LocationLink
----@param offset_encoding string|nil utf-8|utf-16|utf-32
----@param reuse_win boolean|nil Jump to existing window if buffer is already open.
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'?
+---@param reuse_win boolean? Jump to existing window if buffer is already open.
---@return boolean `true` if the jump succeeded
function M.jump_to_location(location, offset_encoding, reuse_win)
- if offset_encoding == nil then
- vim.notify_once(
- 'jump_to_location must be called with valid offset encoding',
- vim.log.levels.WARN
- )
- end
-
+ vim.deprecate('vim.lsp.util.jump_to_location', nil, '0.12')
return M.show_document(location, offset_encoding, { reuse_win = reuse_win, focus = true })
end
@@ -1063,9 +976,9 @@ end
--- - for LocationLink, targetRange is shown (e.g., body of function definition)
---
---@param location lsp.Location|lsp.LocationLink
----@param opts table
----@return integer|nil buffer id of float window
----@return integer|nil window id of float window
+---@param opts? vim.lsp.util.open_floating_preview.Opts
+---@return integer? buffer id of float window
+---@return integer? window id of float window
function M.preview_location(location, opts)
-- location may be LocationLink or Location (more useful for the former)
local uri = location.targetUri or location.uri
@@ -1092,7 +1005,7 @@ end
local function find_window_by_var(name, value)
for _, win in ipairs(api.nvim_list_wins()) do
- if npcall(api.nvim_win_get_var, win, name) == value then
+ if vim.w[win][name] == value then
return win
end
end
@@ -1158,8 +1071,10 @@ local function collapse_blank_lines(contents)
end
local function get_markdown_fences()
- local fences = {}
- for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do
+ local fences = {} --- @type table<string,string>
+ for _, fence in
+ pairs(vim.g.markdown_fenced_languages or {} --[[@as string[] ]])
+ do
local lang, syntax = fence:match('^(.*)=(.*)$')
if lang then
fences[lang] = syntax
@@ -1179,7 +1094,7 @@ end
---
---@param bufnr integer
---@param contents string[] of lines to show in window
----@param opts table with optional fields
+---@param opts? table with optional fields
--- - height of floating window
--- - width of floating window
--- - wrap_at character to wrap at for computing height
@@ -1188,10 +1103,8 @@ end
--- - separator insert separator after code block
---@return table stripped content
function M.stylize_markdown(bufnr, contents, opts)
- validate({
- contents = { contents, 't' },
- opts = { opts, 't', true },
- })
+ validate('contents', contents, 'table')
+ validate('opts', opts, 'table', true)
opts = opts or {}
-- table of fence types to {ft, begin, end}
@@ -1203,8 +1116,11 @@ function M.stylize_markdown(bufnr, contents, opts)
text = { 'text', '<text>', '</text>' },
}
- local match_begin = function(line)
+ --- @param line string
+ --- @return {type:string,ft:string}?
+ local function match_begin(line)
for type, pattern in pairs(matchers) do
+ --- @type string?
local ret = line:match(string.format('^%%s*%s%%s*$', pattern[2]))
if ret then
return {
@@ -1215,7 +1131,10 @@ function M.stylize_markdown(bufnr, contents, opts)
end
end
- local match_end = function(line, match)
+ --- @param line string
+ --- @param match {type:string,ft:string}
+ --- @return string
+ local function match_end(line, match)
local pattern = matchers[match.type]
return line:match(string.format('^%%s*%s%%s*$', pattern[3]))
end
@@ -1224,76 +1143,80 @@ function M.stylize_markdown(bufnr, contents, opts)
contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
local stripped = {}
- local highlights = {}
+ local highlights = {} --- @type {ft:string,start:integer,finish:integer}[]
-- keep track of lnums that contain markdown
- local markdown_lines = {}
- do
- local i = 1
- while i <= #contents do
- local line = contents[i]
- local match = match_begin(line)
- if match then
- local start = #stripped
- i = i + 1
- while i <= #contents do
- line = contents[i]
- if match_end(line, match) then
- i = i + 1
- break
- end
- table.insert(stripped, line)
+ local markdown_lines = {} --- @type table<integer,boolean>
+
+ local i = 1
+ while i <= #contents do
+ local line = contents[i]
+ local match = match_begin(line)
+ if match then
+ local start = #stripped
+ i = i + 1
+ while i <= #contents do
+ line = contents[i]
+ if match_end(line, match) then
i = i + 1
+ break
end
- table.insert(highlights, {
- ft = match.ft,
- start = start + 1,
- finish = #stripped,
- })
- -- add a separator, but not on the last line
- if opts.separator and i < #contents then
- table.insert(stripped, '---')
- markdown_lines[#stripped] = true
- end
- else
- -- strip any empty lines or separators prior to this separator in actual markdown
- if line:match('^---+$') then
- while
- markdown_lines[#stripped]
- and (stripped[#stripped]:match('^%s*$') or stripped[#stripped]:match('^---+$'))
- do
- markdown_lines[#stripped] = false
- table.remove(stripped, #stripped)
- end
- end
- -- add the line if its not an empty line following a separator
- if
- not (
- line:match('^%s*$')
- and markdown_lines[#stripped]
- and stripped[#stripped]:match('^---+$')
- )
- then
- table.insert(stripped, line)
- markdown_lines[#stripped] = true
- end
+ table.insert(stripped, line)
i = i + 1
end
+ table.insert(highlights, {
+ ft = match.ft,
+ start = start + 1,
+ finish = #stripped,
+ })
+ -- add a separator, but not on the last line
+ if opts.separator and i < #contents then
+ table.insert(stripped, '---')
+ markdown_lines[#stripped] = true
+ end
+ else
+ -- strip any empty lines or separators prior to this separator in actual markdown
+ if line:match('^---+$') then
+ while
+ markdown_lines[#stripped]
+ and (stripped[#stripped]:match('^%s*$') or stripped[#stripped]:match('^---+$'))
+ do
+ markdown_lines[#stripped] = false
+ table.remove(stripped, #stripped)
+ end
+ end
+ -- add the line if its not an empty line following a separator
+ if
+ not (
+ line:match('^%s*$')
+ and markdown_lines[#stripped]
+ and stripped[#stripped]:match('^---+$')
+ )
+ then
+ table.insert(stripped, line)
+ markdown_lines[#stripped] = true
+ end
+ i = i + 1
end
end
-- Handle some common html escape sequences
- stripped = vim.tbl_map(function(line)
- local escapes = {
- ['&gt;'] = '>',
- ['&lt;'] = '<',
- ['&quot;'] = '"',
- ['&apos;'] = "'",
- ['&ensp;'] = ' ',
- ['&emsp;'] = ' ',
- ['&amp;'] = '&',
- }
- return (string.gsub(line, '&[^ ;]+;', escapes))
- end, stripped)
+ --- @type string[]
+ stripped = vim.tbl_map(
+ --- @param line string
+ function(line)
+ local escapes = {
+ ['&gt;'] = '>',
+ ['&lt;'] = '<',
+ ['&quot;'] = '"',
+ ['&apos;'] = "'",
+ ['&ensp;'] = ' ',
+ ['&emsp;'] = ' ',
+ ['&amp;'] = '&',
+ }
+ return (line:gsub('&[^ ;]+;', escapes))
+ end,
+ stripped
+ )
-- Compute size of float needed to show (wrapped) lines
opts.wrap_at = opts.wrap_at or (vim.wo['wrap'] and api.nvim_win_get_width(0))
@@ -1312,7 +1235,7 @@ function M.stylize_markdown(bufnr, contents, opts)
local idx = 1
-- keep track of syntaxes we already included.
-- no need to include the same syntax more than once
- local langs = {}
+ local langs = {} --- @type table<string,boolean>
local fences = get_markdown_fences()
local function apply_syntax_to_region(ft, start, finish)
if ft == '' then
@@ -1335,6 +1258,7 @@ function M.stylize_markdown(bufnr, contents, opts)
if #api.nvim_get_runtime_file(('syntax/%s.vim'):format(ft), true) == 0 then
return
end
+ --- @diagnostic disable-next-line:param-type-mismatch
pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft))
langs[lang] = true
end
@@ -1390,10 +1314,8 @@ end
---@return string[] table of lines containing normalized Markdown
---@see https://github.github.com/gfm
function M._normalize_markdown(contents, opts)
- validate({
- contents = { contents, 't' },
- opts = { opts, 't', true },
- })
+ validate('contents', contents, 'table')
+ validate('opts', opts, 'table', true)
opts = opts or {}
-- 1. Carriage returns are removed
@@ -1412,7 +1334,7 @@ end
--- Closes the preview window
---
---@param winnr integer window id of preview window
----@param bufnrs table|nil optional list of ignored buffers
+---@param bufnrs table? optional list of ignored buffers
local function close_preview_window(winnr, bufnrs)
vim.schedule(function()
-- exit if we are in one of ignored buffers
@@ -1460,20 +1382,13 @@ end
---@private
--- Computes size of float needed to show contents (with optional wrapping)
---
----@param contents table of lines to show in window
----@param opts? table with optional fields
---- - height of floating window
---- - width of floating window
---- - wrap_at character to wrap at for computing height
---- - max_width maximal width of floating window
---- - max_height maximal height of floating window
+---@param contents string[] of lines to show in window
+---@param opts? vim.lsp.util.open_floating_preview.Opts
---@return integer width size of float
---@return integer height size of float
function M._make_floating_popup_size(contents, opts)
- validate({
- contents = { contents, 't' },
- opts = { opts, 't', true },
- })
+ validate('contents', contents, 'table')
+ validate('opts', opts, 'table', true)
opts = opts or {}
local width = opts.width
@@ -1481,7 +1396,7 @@ function M._make_floating_popup_size(contents, opts)
local wrap_at = opts.wrap_at
local max_width = opts.max_width
local max_height = opts.max_height
- local line_widths = {}
+ local line_widths = {} --- @type table<integer,integer>
if not width then
width = 0
@@ -1492,17 +1407,15 @@ function M._make_floating_popup_size(contents, opts)
end
end
- local border_width = get_border_size(opts).width
+ local _, border_width = get_border_size(opts)
local screen_width = api.nvim_win_get_width(0)
width = math.min(width, screen_width)
-- make sure borders are always inside the screen
- if width + border_width > screen_width then
- width = width - (width + border_width - screen_width)
- end
+ width = math.min(width, screen_width - border_width)
- if wrap_at and wrap_at > width then
- wrap_at = width
+ if wrap_at then
+ wrap_at = math.min(wrap_at, width)
end
if max_width then
@@ -1534,7 +1447,6 @@ function M._make_floating_popup_size(contents, opts)
end
--- @class vim.lsp.util.open_floating_preview.Opts
---- @inlinedoc
---
--- Height of floating window
--- @field height? integer
@@ -1569,6 +1481,29 @@ end
--- window with the same {focus_id}
--- (default: `true`)
--- @field focus? boolean
+---
+--- offset to add to `col`
+--- @field offset_x? integer
+---
+--- offset to add to `row`
+--- @field offset_y? integer
+--- @field border? string|(string|[string,string])[] override `border`
+--- @field zindex? integer override `zindex`, defaults to 50
+--- @field title? string
+--- @field title_pos? 'left'|'center'|'right'
+---
+--- (default: `'cursor'`)
+--- @field relative? 'mouse'|'cursor'
+---
+--- - "auto": place window based on which side of the cursor has more lines
+--- - "above": place the window above the cursor unless there are not enough lines
+--- to display the full window height.
+--- - "below": place the window below the cursor unless there are not enough lines
+--- to display the full window height.
+--- (default: `'auto'`)
+--- @field anchor_bias? 'auto'|'above'|'below'
+---
+--- @field _update_win? integer
--- Shows contents in a floating window.
---
@@ -1580,11 +1515,9 @@ end
---@return integer bufnr of newly created float window
---@return integer winid of newly created float window preview window
function M.open_floating_preview(contents, syntax, opts)
- validate({
- contents = { contents, 't' },
- syntax = { syntax, 's', true },
- opts = { opts, 't', true },
- })
+ validate('contents', contents, 'table')
+ validate('syntax', syntax, 'string', true)
+ validate('opts', opts, 'table', true)
opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
opts.focus = opts.focus ~= false
@@ -1592,43 +1525,49 @@ function M.open_floating_preview(contents, syntax, opts)
local bufnr = api.nvim_get_current_buf()
- -- check if this popup is focusable and we need to focus
- if opts.focus_id and opts.focusable ~= false and opts.focus then
- -- Go back to previous window if we are in a focusable one
- local current_winnr = api.nvim_get_current_win()
- if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then
- api.nvim_command('wincmd p')
- return bufnr, current_winnr
- end
- do
- local win = find_window_by_var(opts.focus_id, bufnr)
- if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
- -- focus and return the existing buf, win
- api.nvim_set_current_win(win)
- api.nvim_command('stopinsert')
- return api.nvim_win_get_buf(win), win
+ local floating_winnr = opts._update_win
+
+ -- Create/get the buffer
+ local floating_bufnr --- @type integer
+ if floating_winnr then
+ floating_bufnr = api.nvim_win_get_buf(floating_winnr)
+ else
+ -- check if this popup is focusable and we need to focus
+ if opts.focus_id and opts.focusable ~= false and opts.focus then
+ -- Go back to previous window if we are in a focusable one
+ local current_winnr = api.nvim_get_current_win()
+ if vim.w[current_winnr][opts.focus_id] then
+ api.nvim_command('wincmd p')
+ return bufnr, current_winnr
+ end
+ do
+ local win = find_window_by_var(opts.focus_id, bufnr)
+ if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
+ -- focus and return the existing buf, win
+ api.nvim_set_current_win(win)
+ api.nvim_command('stopinsert')
+ return api.nvim_win_get_buf(win), win
+ end
end
end
- end
- -- check if another floating preview already exists for this buffer
- -- and close it if needed
- local existing_float = npcall(api.nvim_buf_get_var, bufnr, 'lsp_floating_preview')
- if existing_float and api.nvim_win_is_valid(existing_float) then
- api.nvim_win_close(existing_float, true)
+ -- check if another floating preview already exists for this buffer
+ -- and close it if needed
+ local existing_float = vim.b[bufnr].lsp_floating_preview
+ if existing_float and api.nvim_win_is_valid(existing_float) then
+ api.nvim_win_close(existing_float, true)
+ end
+ floating_bufnr = api.nvim_create_buf(false, true)
end
- -- Create the buffer
- local floating_bufnr = api.nvim_create_buf(false, true)
-
-- Set up the contents, using treesitter for markdown
local do_stylize = syntax == 'markdown' and vim.g.syntax_on ~= nil
+
if do_stylize then
local width = M._make_floating_popup_size(contents, opts)
contents = M._normalize_markdown(contents, { width = width })
vim.bo[floating_bufnr].filetype = 'markdown'
vim.treesitter.start(floating_bufnr)
- api.nvim_buf_set_lines(floating_bufnr, 0, -1, false, contents)
else
-- Clean up input: trim empty lines
contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
@@ -1636,19 +1575,47 @@ function M.open_floating_preview(contents, syntax, opts)
if syntax then
vim.bo[floating_bufnr].syntax = syntax
end
- api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
end
- -- Compute size of float needed to show (wrapped) lines
- if opts.wrap then
- opts.wrap_at = opts.wrap_at or api.nvim_win_get_width(0)
+ vim.bo[floating_bufnr].modifiable = true
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, false, contents)
+
+ if floating_winnr then
+ api.nvim_win_set_config(floating_winnr, {
+ border = opts.border,
+ title = opts.title,
+ })
else
- opts.wrap_at = nil
- end
- local width, height = M._make_floating_popup_size(contents, opts)
+ -- Compute size of float needed to show (wrapped) lines
+ if opts.wrap then
+ opts.wrap_at = opts.wrap_at or api.nvim_win_get_width(0)
+ else
+ opts.wrap_at = nil
+ end
- local float_option = M.make_floating_popup_options(width, height, opts)
- local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
+ -- TODO(lewis6991): These function assume the current window to determine options,
+ -- therefore it won't work for opts._update_win and the current window if the floating
+ -- window
+ local width, height = M._make_floating_popup_size(contents, opts)
+ local float_option = M.make_floating_popup_options(width, height, opts)
+
+ floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
+
+ api.nvim_buf_set_keymap(
+ floating_bufnr,
+ 'n',
+ 'q',
+ '<cmd>bdelete<cr>',
+ { silent = true, noremap = true, nowait = true }
+ )
+ close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr })
+
+ -- save focus_id
+ if opts.focus_id then
+ api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
+ end
+ api.nvim_buf_set_var(bufnr, 'lsp_floating_preview', floating_winnr)
+ end
if do_stylize then
vim.wo[floating_winnr].conceallevel = 2
@@ -1656,25 +1623,11 @@ function M.open_floating_preview(contents, syntax, opts)
vim.wo[floating_winnr].foldenable = false -- Disable folding.
vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping.
vim.wo[floating_winnr].breakindent = true -- Slightly better list presentation.
+ vim.wo[floating_winnr].smoothscroll = true -- Scroll by screen-line instead of buffer-line.
vim.bo[floating_bufnr].modifiable = false
vim.bo[floating_bufnr].bufhidden = 'wipe'
- api.nvim_buf_set_keymap(
- floating_bufnr,
- 'n',
- 'q',
- '<cmd>bdelete<cr>',
- { silent = true, noremap = true, nowait = true }
- )
- close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr })
-
- -- save focus_id
- if opts.focus_id then
- api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
- end
- api.nvim_buf_set_var(bufnr, 'lsp_floating_preview', floating_winnr)
-
return floating_bufnr, floating_winnr
end
@@ -1683,9 +1636,8 @@ do --[[ References ]]
--- Removes document highlights from a buffer.
---
- ---@param bufnr integer|nil Buffer id
+ ---@param bufnr integer? Buffer id
function M.buf_clear_references(bufnr)
- validate({ bufnr = { bufnr, { 'n' }, true } })
api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1)
end
@@ -1693,29 +1645,18 @@ do --[[ References ]]
---
---@param bufnr integer Buffer id
---@param references lsp.DocumentHighlight[] objects to highlight
- ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32".
+ ---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'
---@see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
function M.buf_highlight_references(bufnr, references, offset_encoding)
- validate({
- bufnr = { bufnr, 'n', true },
- offset_encoding = { offset_encoding, 'string', false },
- })
+ validate('bufnr', bufnr, 'number', true)
+ validate('offset_encoding', offset_encoding, 'string', false)
for _, reference in ipairs(references) do
- local start_line, start_char =
- reference['range']['start']['line'], reference['range']['start']['character']
- local end_line, end_char =
- reference['range']['end']['line'], reference['range']['end']['character']
+ local range = reference.range
+ local start_line = range.start.line
+ local end_line = range['end'].line
- local start_idx = get_line_byte_from_position(
- bufnr,
- { line = start_line, character = start_char },
- offset_encoding
- )
- local end_idx = get_line_byte_from_position(
- bufnr,
- { line = start_line, character = end_char },
- offset_encoding
- )
+ local start_idx = get_line_byte_from_position(bufnr, range.start, offset_encoding)
+ local end_idx = get_line_byte_from_position(bufnr, range['end'], offset_encoding)
local document_highlight_kind = {
[protocol.DocumentHighlightKind.Text] = 'LspReferenceText',
@@ -1723,13 +1664,13 @@ do --[[ References ]]
[protocol.DocumentHighlightKind.Write] = 'LspReferenceWrite',
}
local kind = reference['kind'] or protocol.DocumentHighlightKind.Text
- highlight.range(
+ vim.hl.range(
bufnr,
reference_ns,
document_highlight_kind[kind],
{ start_line, start_idx },
{ end_line, end_idx },
- { priority = vim.highlight.priorities.user }
+ { priority = vim.hl.priorities.user }
)
end
end
@@ -1739,16 +1680,6 @@ local position_sort = sort_by_key(function(v)
return { v.start.line, v.start.character }
end)
----@class vim.lsp.util.locations_to_items.ret
----@inlinedoc
----@field filename string
----@field lnum integer 1-indexed line number
----@field end_lnum integer 1-indexed end line number
----@field col integer 1-indexed column
----@field end_col integer 1-indexed end column
----@field text string
----@field user_data lsp.Location|lsp.LocationLink
-
--- Returns the items with the byte position calculated correctly and in sorted
--- order, for display in quickfix and location lists.
---
@@ -1759,9 +1690,9 @@ end)
--- |setloclist()|.
---
---@param locations lsp.Location[]|lsp.LocationLink[]
----@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32
---- default to first client of buffer
----@return vim.lsp.util.locations_to_items.ret[]
+---@param offset_encoding? 'utf-8'|'utf-16'|'utf-32'
+--- default to first client of buffer
+---@return vim.quickfix.entry[] # See |setqflist()| for the format
function M.locations_to_items(locations, offset_encoding)
if offset_encoding == nil then
vim.notify_once(
@@ -1771,28 +1702,19 @@ function M.locations_to_items(locations, offset_encoding)
offset_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding
end
- local items = {}
+ local items = {} --- @type vim.quickfix.entry[]
+
---@type table<string, {start: lsp.Position, end: lsp.Position, location: lsp.Location|lsp.LocationLink}[]>
- local grouped = setmetatable({}, {
- __index = function(t, k)
- local v = {}
- rawset(t, k, v)
- return v
- end,
- })
+ local grouped = {}
for _, d in ipairs(locations) do
-- locations may be Location or LocationLink
local uri = d.uri or d.targetUri
local range = d.range or d.targetSelectionRange
+ grouped[uri] = grouped[uri] or {}
table.insert(grouped[uri], { start = range.start, ['end'] = range['end'], location = d })
end
- ---@type string[]
- local keys = vim.tbl_keys(grouped)
- table.sort(keys)
- -- TODO(ashkan) I wish we could do this lazily.
- for _, uri in ipairs(keys) do
- local rows = grouped[uri]
+ for uri, rows in vim.spairs(grouped) do
table.sort(rows, position_sort)
local filename = vim.uri_to_fname(uri)
@@ -1814,10 +1736,10 @@ function M.locations_to_items(locations, offset_encoding)
local end_row = end_pos.line
local line = lines[row] or ''
local end_line = lines[end_row] or ''
- local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
- local end_col = M._str_byteindex_enc(end_line, end_pos.character, offset_encoding)
+ local col = vim.str_byteindex(line, offset_encoding, pos.character, false)
+ local end_col = vim.str_byteindex(end_line, offset_encoding, end_pos.character, false)
- table.insert(items, {
+ items[#items + 1] = {
filename = filename,
lnum = row + 1,
end_lnum = end_row + 1,
@@ -1825,58 +1747,51 @@ function M.locations_to_items(locations, offset_encoding)
end_col = end_col + 1,
text = line,
user_data = temp.location,
- })
+ }
end
end
return items
end
--- According to LSP spec, if the client set "symbolKind.valueSet",
--- the client must handle it properly even if it receives a value outside the specification.
--- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
-function M._get_symbol_kind_name(symbol_kind)
- return protocol.SymbolKind[symbol_kind] or 'Unknown'
-end
-
--- Converts symbols to quickfix list items.
---
----@param symbols table DocumentSymbol[] or SymbolInformation[]
+---@param symbols lsp.DocumentSymbol[]|lsp.SymbolInformation[]
---@param bufnr? integer
+---@return vim.quickfix.entry[] # See |setqflist()| for the format
function M.symbols_to_items(symbols, bufnr)
- local function _symbols_to_items(_symbols, _items, _bufnr)
- for _, symbol in ipairs(_symbols) do
- if symbol.location then -- SymbolInformation type
- local range = symbol.location.range
- local kind = M._get_symbol_kind_name(symbol.kind)
- table.insert(_items, {
- filename = vim.uri_to_fname(symbol.location.uri),
- lnum = range.start.line + 1,
- col = range.start.character + 1,
- kind = kind,
- text = '[' .. kind .. '] ' .. symbol.name,
- })
- elseif symbol.selectionRange then -- DocumentSymbole type
- local kind = M._get_symbol_kind_name(symbol.kind)
- table.insert(_items, {
- -- bufnr = _bufnr,
- filename = api.nvim_buf_get_name(_bufnr),
- lnum = symbol.selectionRange.start.line + 1,
- col = symbol.selectionRange.start.character + 1,
- kind = kind,
- text = '[' .. kind .. '] ' .. symbol.name,
- })
- if symbol.children then
- for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do
- for _, s in ipairs(v) do
- table.insert(_items, s)
- end
- end
- end
- end
+ bufnr = bufnr or 0
+ local items = {} --- @type vim.quickfix.entry[]
+ for _, symbol in ipairs(symbols) do
+ --- @type string?, lsp.Position?
+ local filename, pos
+
+ if symbol.location then
+ --- @cast symbol lsp.SymbolInformation
+ filename = vim.uri_to_fname(symbol.location.uri)
+ pos = symbol.location.range.start
+ elseif symbol.selectionRange then
+ --- @cast symbol lsp.DocumentSymbol
+ filename = api.nvim_buf_get_name(bufnr)
+ pos = symbol.selectionRange.start
+ end
+
+ if filename and pos then
+ local kind = protocol.SymbolKind[symbol.kind] or 'Unknown'
+ items[#items + 1] = {
+ filename = filename,
+ lnum = pos.line + 1,
+ col = pos.character + 1,
+ kind = kind,
+ text = '[' .. kind .. '] ' .. symbol.name,
+ }
+ end
+
+ if symbol.children then
+ list_extend(items, M.symbols_to_items(symbol.children, bufnr))
end
- return _items
end
- return _symbols_to_items(symbols, {}, bufnr or 0)
+
+ return items
end
--- Removes empty lines from the beginning and end.
@@ -1899,7 +1814,7 @@ function M.trim_empty_lines(lines)
break
end
end
- return list_extend({}, lines, start, finish)
+ return vim.list_slice(lines, start, finish)
end
--- Accepts markdown lines and tries to reduce them to a filetype if they
@@ -1932,8 +1847,8 @@ function M.try_trim_markdown_code_blocks(lines)
return 'markdown'
end
----@param window integer|nil: window handle or 0 for current, defaults to current
----@param offset_encoding? string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window`
+---@param window integer?: window handle or 0 for current, defaults to current
+---@param offset_encoding? 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of buffer of `window`
local function make_position_param(window, offset_encoding)
window = window or 0
local buf = api.nvim_win_get_buf(window)
@@ -1945,15 +1860,15 @@ local function make_position_param(window, offset_encoding)
return { line = 0, character = 0 }
end
- col = M._str_utfindex_enc(line, col, offset_encoding)
+ col = vim.str_utfindex(line, offset_encoding, col, false)
return { line = row, character = col }
end
--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position.
---
----@param window integer|nil: window handle or 0 for current, defaults to current
----@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window`
+---@param window integer?: window handle or 0 for current, defaults to current
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of buffer of `window`
---@return lsp.TextDocumentPositionParams
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
function M.make_position_params(window, offset_encoding)
@@ -1970,11 +1885,9 @@ end
---@param bufnr integer buffer handle or 0 for current, defaults to current
---@return string encoding first client if there is one, nil otherwise
function M._get_offset_encoding(bufnr)
- validate({
- bufnr = { bufnr, 'n', true },
- })
+ validate('bufnr', bufnr, 'number', true)
- local offset_encoding
+ local offset_encoding --- @type 'utf-8'|'utf-16'|'utf-32'?
for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
if client.offset_encoding == nil then
@@ -2005,8 +1918,8 @@ end
--- `textDocument/codeAction`, `textDocument/colorPresentation`,
--- `textDocument/rangeFormatting`.
---
----@param window integer|nil: window handle or 0 for current, defaults to current
----@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of buffer of `window`
+---@param window integer? window handle or 0 for current, defaults to current
+---@param offset_encoding "utf-8"|"utf-16"|"utf-32"? defaults to `offset_encoding` of first client of buffer of `window`
---@return table { textDocument = { uri = `current_file_uri` }, range = { start =
---`current_position`, end = `current_position` } }
function M.make_range_params(window, offset_encoding)
@@ -2022,33 +1935,33 @@ end
--- Using the given range in the current buffer, creates an object that
--- is similar to |vim.lsp.util.make_range_params()|.
---
----@param start_pos integer[]|nil {row,col} mark-indexed position.
+---@param start_pos [integer,integer]? {row,col} mark-indexed position.
--- Defaults to the start of the last visual selection.
----@param end_pos integer[]|nil {row,col} mark-indexed position.
+---@param end_pos [integer,integer]? {row,col} mark-indexed position.
--- Defaults to the end of the last visual selection.
----@param bufnr integer|nil buffer handle or 0 for current, defaults to current
----@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of `bufnr`
+---@param bufnr integer? buffer handle or 0 for current, defaults to current
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? defaults to `offset_encoding` of first client of `bufnr`
---@return table { textDocument = { uri = `current_file_uri` }, range = { start =
---`start_position`, end = `end_position` } }
function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
- validate({
- start_pos = { start_pos, 't', true },
- end_pos = { end_pos, 't', true },
- offset_encoding = { offset_encoding, 's', true },
- })
+ validate('start_pos', start_pos, 'table', true)
+ validate('end_pos', end_pos, 'table', true)
+ validate('offset_encoding', offset_encoding, 'string', true)
bufnr = bufnr or api.nvim_get_current_buf()
offset_encoding = offset_encoding or M._get_offset_encoding(bufnr)
- local A = list_extend({}, start_pos or api.nvim_buf_get_mark(bufnr, '<'))
- local B = list_extend({}, end_pos or api.nvim_buf_get_mark(bufnr, '>'))
+ --- @type [integer, integer]
+ local A = { unpack(start_pos or api.nvim_buf_get_mark(bufnr, '<')) }
+ --- @type [integer, integer]
+ local B = { unpack(end_pos or api.nvim_buf_get_mark(bufnr, '>')) }
-- convert to 0-index
A[1] = A[1] - 1
B[1] = B[1] - 1
-- account for offset_encoding.
if A[2] > 0 then
- A = { A[1], M.character_offset(bufnr, A[1], A[2], offset_encoding) }
+ A[2] = M.character_offset(bufnr, A[1], A[2], offset_encoding)
end
if B[2] > 0 then
- B = { B[1], M.character_offset(bufnr, B[1], B[2], offset_encoding) }
+ B[2] = M.character_offset(bufnr, B[1], B[2], offset_encoding)
end
-- we need to offset the end character position otherwise we loose the last
-- character of the selection, as LSP end position is exclusive
@@ -2067,7 +1980,7 @@ end
--- Creates a `TextDocumentIdentifier` object for the current buffer.
---
----@param bufnr integer|nil: Buffer handle, defaults to current
+---@param bufnr integer?: Buffer handle, defaults to current
---@return lsp.TextDocumentIdentifier
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier
function M.make_text_document_params(bufnr)
@@ -2085,10 +1998,10 @@ end
--- Returns indentation size.
---
---@see 'shiftwidth'
----@param bufnr integer|nil: Buffer handle, defaults to current
+---@param bufnr integer?: Buffer handle, defaults to current
---@return integer indentation size
function M.get_effective_tabstop(bufnr)
- validate({ bufnr = { bufnr, 'n', true } })
+ validate('bufnr', bufnr, 'number', true)
local bo = bufnr and vim.bo[bufnr] or vim.bo
local sw = bo.shiftwidth
return (sw == 0 and bo.tabstop) or sw
@@ -2096,11 +2009,11 @@ end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
---
----@param options lsp.FormattingOptions|nil with valid `FormattingOptions` entries
+---@param options lsp.FormattingOptions? with valid `FormattingOptions` entries
---@return lsp.DocumentFormattingParams object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options)
- validate({ options = { options, 't', true } })
+ validate('options', options, 'table', true)
options = vim.tbl_extend('keep', options or {}, {
tabSize = M.get_effective_tabstop(),
insertSpaces = vim.bo.expandtab,
@@ -2116,7 +2029,8 @@ end
---@param buf integer buffer number (0 for current)
---@param row integer 0-indexed line
---@param col integer 0-indexed byte offset in line
----@param offset_encoding string utf-8|utf-16|utf-32 defaults to `offset_encoding` of first client of `buf`
+---@param offset_encoding? 'utf-8'|'utf-16'|'utf-32'
+--- defaults to `offset_encoding` of first client of `buf`
---@return integer `offset_encoding` index of the character in line {row} column {col} in buffer {buf}
function M.character_offset(buf, row, col, offset_encoding)
local line = get_line(buf, row)
@@ -2127,7 +2041,7 @@ function M.character_offset(buf, row, col, offset_encoding)
)
offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding
end
- return M._str_utfindex_enc(line, col, offset_encoding)
+ return vim.str_utfindex(line, offset_encoding, col, false)
end
--- Helper function to return nested values in language server settings
@@ -2139,6 +2053,7 @@ end
function M.lookup_section(settings, section)
vim.deprecate('vim.lsp.util.lookup_section()', 'vim.tbl_get() with `vim.split`', '0.12')
for part in vim.gsplit(section, '.', { plain = true }) do
+ --- @diagnostic disable-next-line:no-unknown
settings = settings[part]
if settings == nil then
return vim.NIL
@@ -2153,7 +2068,7 @@ end
---@param bufnr integer
---@param start_line integer
---@param end_line integer
----@param offset_encoding lsp.PositionEncodingKind
+---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'
---@return lsp.Range
local function make_line_range_params(bufnr, start_line, end_line, offset_encoding)
local last_line = api.nvim_buf_line_count(bufnr) - 1
@@ -2161,7 +2076,7 @@ local function make_line_range_params(bufnr, start_line, end_line, offset_encodi
---@type lsp.Position
local end_pos
- if end_line == last_line and not vim.api.nvim_get_option_value('endofline', { buf = bufnr }) then
+ if end_line == last_line and not vim.bo[bufnr].endofline then
end_pos = {
line = end_line,
character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding),
@@ -2201,9 +2116,7 @@ function M._refresh(method, opts)
local textDocument = M.make_text_document_params(bufnr)
- local only_visible = opts.only_visible or false
-
- if only_visible then
+ if opts.only_visible then
for _, window in ipairs(api.nvim_list_wins()) do
if api.nvim_win_get_buf(window) == bufnr then
local first = vim.fn.line('w0', window)
diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua
index 47c2080e3c..5ecb00f49b 100644
--- a/runtime/lua/vim/provider/health.lua
+++ b/runtime/lua/vim/provider/health.lua
@@ -449,7 +449,7 @@ end
--- Get the latest Nvim Python client (pynvim) version from PyPI.
local function latest_pypi_version()
local pypi_version = 'unable to get pypi response'
- local pypi_response = download('https://pypi.python.org/pypi/pynvim/json')
+ local pypi_response = download('https://pypi.org/pypi/pynvim/json')
if pypi_response ~= '' then
local pcall_ok, output = pcall(vim.fn.json_decode, pypi_response)
if not pcall_ok then
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index 266725cce2..7b1d071270 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -26,7 +26,7 @@ end
---
---@param trust table<string, string> Trust table to write
local function write_trust(trust)
- vim.validate({ trust = { trust, 't' } })
+ vim.validate('trust', trust, 'table')
local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w'))
local t = {} ---@type string[]
@@ -49,7 +49,7 @@ end
---@return (string|nil) The contents of the given file if it exists and is
--- trusted, or nil otherwise.
function M.read(path)
- vim.validate({ path = { path, 's' } })
+ vim.validate('path', path, 'string')
local fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
if not fullpath then
return nil
@@ -132,17 +132,11 @@ end
---@return boolean success true if operation was successful
---@return string msg full path if operation was successful, else error message
function M.trust(opts)
- vim.validate({
- path = { opts.path, 's', true },
- bufnr = { opts.bufnr, 'n', true },
- action = {
- opts.action,
- function(m)
- return m == 'allow' or m == 'deny' or m == 'remove'
- end,
- [["allow" or "deny" or "remove"]],
- },
- })
+ vim.validate('path', opts.path, 'string', true)
+ vim.validate('bufnr', opts.bufnr, 'number', true)
+ vim.validate('action', opts.action, function(m)
+ return m == 'allow' or m == 'deny' or m == 'remove'
+ end, [["allow" or "deny" or "remove"]])
---@cast opts vim.trust.opts
local path = opts.path
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 4d06cdd77d..4f2373b182 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -109,7 +109,9 @@ function vim.gsplit(s, sep, opts)
if type(opts) == 'boolean' then
plain = opts -- For backwards compatibility.
else
- vim.validate({ s = { s, 's' }, sep = { sep, 's' }, opts = { opts, 't', true } })
+ vim.validate('s', s, 'string')
+ vim.validate('sep', sep, 'string')
+ vim.validate('opts', opts, 'table', true)
opts = opts or {}
plain, trimempty = opts.plain, opts.trimempty
end
@@ -249,7 +251,8 @@ end
---@param t table<any, T> Table
---@return table : Table of transformed values
function vim.tbl_map(func, t)
- vim.validate({ func = { func, 'c' }, t = { t, 't' } })
+ vim.validate('func', func, 'callable')
+ vim.validate('t', t, 'table')
--- @cast t table<any,any>
local rettab = {} --- @type table<any,any>
@@ -266,7 +269,8 @@ end
---@param t table<any, T> (table) Table
---@return T[] : Table of filtered values
function vim.tbl_filter(func, t)
- vim.validate({ func = { func, 'c' }, t = { t, 't' } })
+ vim.validate('func', func, 'callable')
+ vim.validate('t', t, 'table')
--- @cast t table<any,any>
local rettab = {} --- @type table<any,any>
@@ -303,12 +307,13 @@ end
---@param opts? vim.tbl_contains.Opts Keyword arguments |kwargs|:
---@return boolean `true` if `t` contains `value`
function vim.tbl_contains(t, value, opts)
- vim.validate({ t = { t, 't' }, opts = { opts, 't', true } })
+ vim.validate('t', t, 'table')
+ vim.validate('opts', opts, 'table', true)
--- @cast t table<any,any>
local pred --- @type fun(v: any): boolean?
if opts and opts.predicate then
- vim.validate({ value = { value, 'c' } })
+ vim.validate('value', value, 'callable')
pred = value
else
pred = function(v)
@@ -550,12 +555,10 @@ end
---@param finish integer? Final index on src. Defaults to `#src`
---@return T dst
function vim.list_extend(dst, src, start, finish)
- vim.validate({
- dst = { dst, 't' },
- src = { src, 't' },
- start = { start, 'n', true },
- finish = { finish, 'n', true },
- })
+ vim.validate('dst', dst, 'table')
+ vim.validate('src', src, 'table')
+ vim.validate('start', start, 'number', true)
+ vim.validate('finish', finish, 'number', true)
for i = start or 1, finish or #src do
table.insert(dst, src[i])
end
@@ -778,231 +781,227 @@ function vim.endswith(s, suffix)
end
do
- --- @alias vim.validate.Type
- --- | 't' | 'table'
- --- | 's' | 'string'
- --- | 'n' | 'number'
- --- | 'f' | 'function'
- --- | 'c' | 'callable'
- --- | 'nil'
- --- | 'thread'
- --- | 'userdata
-
- local type_names = {
- ['table'] = 'table',
- t = 'table',
- ['string'] = 'string',
- s = 'string',
- ['number'] = 'number',
- n = 'number',
- ['boolean'] = 'boolean',
+ --- @alias vim.validate.Validator
+ --- | type
+ --- | 'callable'
+ --- | (type|'callable')[]
+ --- | fun(v:any):boolean, string?
+
+ local type_aliases = {
b = 'boolean',
- ['function'] = 'function',
- f = 'function',
- ['callable'] = 'callable',
c = 'callable',
- ['nil'] = 'nil',
- ['thread'] = 'thread',
- ['userdata'] = 'userdata',
+ f = 'function',
+ n = 'number',
+ s = 'string',
+ t = 'table',
}
--- @nodoc
- --- @class vim.validate.Spec [any, string|string[], boolean]
+ --- @class vim.validate.Spec
--- @field [1] any Argument value
- --- @field [2] string|string[]|fun(v:any):boolean, string? Type name, or callable
- --- @field [3]? boolean
+ --- @field [2] vim.validate.Validator Argument validator
+ --- @field [3]? boolean|string Optional flag or error message
- local function _is_type(val, t)
+ local function is_type(val, t)
return type(val) == t or (t == 'callable' and vim.is_callable(val))
end
--- @param param_name string
- --- @param spec vim.validate.Spec
+ --- @param val any
+ --- @param validator vim.validate.Validator
+ --- @param message? string
+ --- @param allow_alias? boolean Allow short type names: 'n', 's', 't', 'b', 'f', 'c'
--- @return string?
- local function is_param_valid(param_name, spec)
- if type(spec) ~= 'table' then
- return string.format('opt[%s]: expected table, got %s', param_name, type(spec))
- end
+ local function is_valid(param_name, val, validator, message, allow_alias)
+ if type(validator) == 'string' then
+ local expected = allow_alias and type_aliases[validator] or validator
- local val = spec[1] -- Argument value
- local types = spec[2] -- Type name, or callable
- local optional = (true == spec[3])
-
- if type(types) == 'string' then
- types = { types }
- end
+ if not expected then
+ return string.format('invalid type name: %s', validator)
+ end
- if vim.is_callable(types) then
+ if not is_type(val, expected) then
+ return string.format('%s: expected %s, got %s', param_name, expected, type(val))
+ end
+ elseif vim.is_callable(validator) then
-- Check user-provided validation function
- local valid, optional_message = types(val)
+ local valid, opt_msg = validator(val)
if not valid then
- local error_message =
- string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val))
- if optional_message ~= nil then
- error_message = string.format('%s. Info: %s', error_message, optional_message)
+ local err_msg =
+ string.format('%s: expected %s, got %s', param_name, message or '?', tostring(val))
+
+ if opt_msg then
+ err_msg = string.format('%s. Info: %s', err_msg, opt_msg)
end
- return error_message
+ return err_msg
end
- elseif type(types) == 'table' then
- local success = false
- for i, t in ipairs(types) do
- local t_name = type_names[t]
- if not t_name then
+ elseif type(validator) == 'table' then
+ for _, t in ipairs(validator) do
+ local expected = allow_alias and type_aliases[t] or t
+ if not expected then
return string.format('invalid type name: %s', t)
end
- types[i] = t_name
- if (optional and val == nil) or _is_type(val, t_name) then
- success = true
- break
+ if is_type(val, expected) then
+ return -- success
end
end
- if not success then
- return string.format(
- '%s: expected %s, got %s',
- param_name,
- table.concat(types, '|'),
- type(val)
- )
+
+ -- Normalize validator types for error message
+ if allow_alias then
+ for i, t in ipairs(validator) do
+ validator[i] = type_aliases[t] or t
+ end
end
+
+ return string.format(
+ '%s: expected %s, got %s',
+ param_name,
+ table.concat(validator, '|'),
+ type(val)
+ )
else
- return string.format('invalid type name: %s', tostring(types))
+ return string.format('invalid validator: %s', tostring(validator))
end
end
- --- @param opt table<vim.validate.Type,vim.validate.Spec>
- --- @return boolean, string?
- local function is_valid(opt)
- if type(opt) ~= 'table' then
- return false, string.format('opt: expected table, got %s', type(opt))
- end
-
+ --- @param opt table<type|'callable',vim.validate.Spec>
+ --- @return string?
+ local function validate_spec(opt)
local report --- @type table<string,string>?
for param_name, spec in pairs(opt) do
- local msg = is_param_valid(param_name, spec)
- if msg then
+ local err_msg --- @type string?
+ if type(spec) ~= 'table' then
+ err_msg = string.format('opt[%s]: expected table, got %s', param_name, type(spec))
+ else
+ local value, validator = spec[1], spec[2]
+ local msg = type(spec[3]) == 'string' and spec[3] or nil --[[@as string?]]
+ local optional = spec[3] == true
+ if not (optional and value == nil) then
+ err_msg = is_valid(param_name, value, validator, msg, true)
+ end
+ end
+
+ if err_msg then
report = report or {}
- report[param_name] = msg
+ report[param_name] = err_msg
end
end
if report then
for _, msg in vim.spairs(report) do -- luacheck: ignore
- return false, msg
+ return msg
end
end
-
- return true
end
--- Validate function arguments.
---
--- This function has two valid forms:
---
- --- 1. vim.validate(name: str, value: any, type: string, optional?: bool)
- --- 2. vim.validate(spec: table)
+ --- 1. `vim.validate(name, value, validator[, optional][, message])`
---
- --- Form 1 validates that argument {name} with value {value} has the type
- --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is
- --- true, then {value} may be null. This form is significantly faster and
- --- should be preferred for simple cases.
+ --- Validates that argument {name} with value {value} satisfies
+ --- {validator}. If {optional} is given and is `true`, then {value} may be
+ --- `nil`. If {message} is given, then it is used as the expected type in the
+ --- error message.
---
- --- Example:
+ --- Example:
---
- --- ```lua
- --- function vim.startswith(s, prefix)
- --- vim.validate('s', s, 'string')
- --- vim.validate('prefix', prefix, 'string')
- --- ...
- --- end
- --- ```
+ --- ```lua
+ --- function vim.startswith(s, prefix)
+ --- vim.validate('s', s, 'string')
+ --- vim.validate('prefix', prefix, 'string')
+ --- ...
+ --- end
+ --- ```
---
- --- Form 2 validates a parameter specification (types and values). Specs are
- --- evaluated in alphanumeric order, until the first failure.
+ --- 2. `vim.validate(spec)` (deprecated)
+ --- where `spec` is of type
+ --- `table<string,[value:any, validator: vim.validate.Validator, optional_or_msg? : boolean|string]>)`
---
- --- Usage example:
+ --- Validates a argument specification.
+ --- Specs are evaluated in alphanumeric order, until the first failure.
---
- --- ```lua
- --- function user.new(name, age, hobbies)
- --- vim.validate{
- --- name={name, 'string'},
- --- age={age, 'number'},
- --- hobbies={hobbies, 'table'},
- --- }
- --- ...
- --- end
- --- ```
+ --- Example:
+ ---
+ --- ```lua
+ --- function user.new(name, age, hobbies)
+ --- vim.validate{
+ --- name={name, 'string'},
+ --- age={age, 'number'},
+ --- hobbies={hobbies, 'table'},
+ --- }
+ --- ...
+ --- end
+ --- ```
---
--- Examples with explicit argument values (can be run directly):
---
--- ```lua
- --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
+ --- vim.validate('arg1', {'foo'}, 'table')
+ --- --> NOP (success)
+ --- vim.validate('arg2', 'foo', 'string')
--- --> NOP (success)
---
- --- vim.validate{arg1={1, 'table'}}
+ --- vim.validate('arg1', 1, 'table')
--- --> error('arg1: expected table, got number')
---
- --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
+ --- vim.validate('arg1', 3, function(a) return (a % 2) == 0 end, 'even number')
--- --> error('arg1: expected even number, got 3')
--- ```
---
--- If multiple types are valid they can be given as a list.
---
--- ```lua
- --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
+ --- vim.validate('arg1', {'foo'}, {'table', 'string'})
+ --- vim.validate('arg2', 'foo', {'table', 'string'})
--- -- NOP (success)
---
- --- vim.validate{arg1={1, {'string', 'table'}}}
+ --- vim.validate('arg1', 1, {'string', 'table'})
--- -- error('arg1: expected string|table, got number')
--- ```
---
- ---@param opt table<vim.validate.Type,vim.validate.Spec> (table) Names of parameters to validate. Each key is a parameter
- --- name; each value is a tuple in one of these forms:
- --- 1. (arg_value, type_name, optional)
- --- - arg_value: argument value
- --- - type_name: string|table type name, one of: ("table", "t", "string",
- --- "s", "number", "n", "boolean", "b", "function", "f", "nil",
- --- "thread", "userdata") or list of them.
- --- - optional: (optional) boolean, if true, `nil` is valid
- --- 2. (arg_value, fn, msg)
- --- - arg_value: argument value
- --- - fn: any function accepting one argument, returns true if and
- --- only if the argument is valid. Can optionally return an additional
- --- informative error message as the second returned value.
- --- - msg: (optional) error string if validation fails
- --- @overload fun(name: string, val: any, expected: string, optional?: boolean)
- function vim.validate(opt, ...)
- local ok = false
- local err_msg ---@type string?
- local narg = select('#', ...)
- if narg == 0 then
- ok, err_msg = is_valid(opt)
- elseif narg >= 2 then
- -- Overloaded signature for fast/simple cases
- local name = opt --[[@as string]]
- local v, expected, optional = ... ---@type string, string, boolean?
- local actual = type(v)
-
- ok = (actual == expected) or (v == nil and optional == true)
+ --- @note `validator` set to a value returned by |lua-type()| provides the
+ --- best performance.
+ ---
+ --- @param name string Argument name
+ --- @param value string Argument value
+ --- @param validator vim.validate.Validator
+ --- - (`string|string[]`): Any value that can be returned from |lua-type()| in addition to
+ --- `'callable'`: `'boolean'`, `'callable'`, `'function'`, `'nil'`, `'number'`, `'string'`, `'table'`,
+ --- `'thread'`, `'userdata'`.
+ --- - (`fun(val:any): boolean, string?`) A function that returns a boolean and an optional
+ --- string message.
+ --- @param optional? boolean Argument is optional (may be omitted)
+ --- @param message? string message when validation fails
+ --- @overload fun(name: string, val: any, validator: vim.validate.Validator, message: string)
+ --- @overload fun(spec: table<string,[any, vim.validate.Validator, boolean|string]>)
+ function vim.validate(name, value, validator, optional, message)
+ local err_msg --- @type string?
+ if validator then -- Form 1
+ -- Check validator as a string first to optimize the common case.
+ local ok = (type(value) == validator) or (value == nil and optional == true)
if not ok then
- err_msg = ('%s: expected %s, got %s%s'):format(
- name,
- expected,
- actual,
- v and (' (%s)'):format(v) or ''
- )
+ local msg = type(optional) == 'string' and optional or message --[[@as string?]]
+ -- Check more complicated validators
+ err_msg = is_valid(name, value, validator, msg, false)
end
+ elseif type(name) == 'table' then -- Form 2
+ vim.deprecate('vim.validate', 'vim.validate(name, value, validator, optional_or_msg)', '1.0')
+ err_msg = validate_spec(name)
else
error('invalid arguments')
end
- if not ok then
+ if err_msg then
error(err_msg, 2)
end
end
end
+
--- Returns true if object `f` can be called as a function.
---
---@param f any Any object
@@ -1143,7 +1142,7 @@ end
--- @param mod T
--- @return T
function vim._defer_require(root, mod)
- return setmetatable({}, {
+ return setmetatable({ _submodules = mod }, {
---@param t table<string, any>
---@param k string
__index = function(t, k)
@@ -1157,6 +1156,34 @@ function vim._defer_require(root, mod)
})
end
+--- @private
+--- Creates a module alias/shim that lazy-loads a target module.
+---
+--- Unlike `vim.defaulttable()` this also:
+--- - implements __call
+--- - calls vim.deprecate()
+---
+--- @param old_name string Name of the deprecated module, which will be shimmed.
+--- @param new_name string Name of the new module, which will be loaded by require().
+function vim._defer_deprecated_module(old_name, new_name)
+ return setmetatable({}, {
+ ---@param _ table<string, any>
+ ---@param k string
+ __index = function(_, k)
+ vim.deprecate(old_name, new_name, '2.0.0', nil, false)
+ --- @diagnostic disable-next-line:no-unknown
+ local target = require(new_name)
+ return target[k]
+ end,
+ __call = function(self)
+ vim.deprecate(old_name, new_name, '2.0.0', nil, false)
+ --- @diagnostic disable-next-line:no-unknown
+ local target = require(new_name)
+ return target(self)
+ end,
+ })
+end
+
--- @nodoc
--- @class vim.context.mods
--- @field bo? table<string, any>
@@ -1193,11 +1220,14 @@ local state_restore_order = { 'bo', 'wo', 'go', 'env' }
--- @param context vim.context.mods
--- @return vim.context.state
local get_context_state = function(context)
+ --- @type vim.context.state
local res = { bo = {}, env = {}, go = {}, wo = {} }
-- Use specific order from possibly most to least intrusive
for _, scope in ipairs(scope_order) do
- for name, _ in pairs(context[scope] or {}) do
+ for name, _ in
+ pairs(context[scope] or {} --[[@as table<string,any>]])
+ do
local sc = scope == 'o' and scope_map[vim.api.nvim_get_option_info2(name, {}).scope] or scope
-- Do not override already set state and fall back to `vim.NIL` for
@@ -1291,7 +1321,10 @@ function vim._with(context, f)
-- Apply some parts of the context in specific order
-- NOTE: triggers `OptionSet` event
for _, scope in ipairs(scope_order) do
- for name, context_value in pairs(context[scope] or {}) do
+ for name, context_value in
+ pairs(context[scope] or {} --[[@as table<string,any>]])
+ do
+ --- @diagnostic disable-next-line:no-unknown
vim[scope][name] = context_value
end
end
@@ -1302,7 +1335,10 @@ function vim._with(context, f)
-- Restore relevant cached values in specific order, global scope last
-- NOTE: triggers `OptionSet` event
for _, scope in ipairs(state_restore_order) do
- for name, cached_value in pairs(state[scope]) do
+ for name, cached_value in
+ pairs(state[scope] --[[@as table<string,any>]])
+ do
+ --- @diagnostic disable-next-line:no-unknown
vim[scope][name] = cached_value
end
end
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
index 1da2e71839..4aa41bba9b 100644
--- a/runtime/lua/vim/termcap.lua
+++ b/runtime/lua/vim/termcap.lua
@@ -17,10 +17,8 @@ local M = {}
--- otherwise. {seq} is the control sequence for the capability if found, or nil for
--- boolean capabilities.
function M.query(caps, cb)
- vim.validate({
- caps = { caps, { 'string', 'table' } },
- cb = { cb, 'f' },
- })
+ vim.validate('caps', caps, { 'string', 'table' })
+ vim.validate('cb', cb, 'function')
if type(caps) ~= 'table' then
caps = { caps }
@@ -40,7 +38,7 @@ function M.query(caps, cb)
local k, rest = resp:match('^\027P1%+r(%x+)(.*)$')
if k and rest then
local cap = vim.text.hexdecode(k)
- if not pending[cap] then
+ if not cap or not pending[cap] then
-- Received a response for a capability we didn't request. This can happen if there are
-- multiple concurrent XTGETTCAP requests
return
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index ed7d31e1f7..dca89f413c 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -133,10 +133,8 @@ end
---
---@return vim.treesitter.LanguageTree object to use for parsing
function M.get_string_parser(str, lang, opts)
- vim.validate({
- str = { str, 'string' },
- lang = { lang, 'string' },
- })
+ vim.validate('str', str, 'string')
+ vim.validate('lang', lang, 'string')
return LanguageTree.new(str, lang, opts)
end
@@ -152,8 +150,7 @@ function M.is_ancestor(dest, source)
return false
end
- -- child_containing_descendant returns nil if dest is a direct parent
- return source:parent() == dest or dest:child_containing_descendant(source) ~= nil
+ return dest:child_with_descendant(source) ~= nil
end
--- Returns the node's range or an unpacked range table
@@ -168,7 +165,7 @@ function M.get_node_range(node_or_range)
if type(node_or_range) == 'table' then
return unpack(node_or_range)
else
- return node_or_range:range()
+ return node_or_range:range(false)
end
end
@@ -244,11 +241,9 @@ end
---
---@return boolean True if the {node} contains the {range}
function M.node_contains(node, range)
- vim.validate({
- -- allow a table so nodes can be mocked
- node = { node, { 'userdata', 'table' } },
- range = { range, M._range.validate, 'integer list with 4 or 6 elements' },
- })
+ -- allow a table so nodes can be mocked
+ vim.validate('node', node, { 'userdata', 'table' })
+ vim.validate('range', range, M._range.validate, 'integer list with 4 or 6 elements')
return M._range.contains({ node:range() }, range)
end
diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua
index acc9f8d24e..d982b6a505 100644
--- a/runtime/lua/vim/treesitter/_meta/tsnode.lua
+++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua
@@ -15,7 +15,7 @@ error('Cannot require a meta file')
local TSNode = {} -- luacheck: no unused
--- Get the node's immediate parent.
---- Prefer |TSNode:child_containing_descendant()|
+--- Prefer |TSNode:child_with_descendant()|
--- for iterating over the node's ancestors.
--- @return TSNode?
function TSNode:parent() end
@@ -71,8 +71,24 @@ function TSNode:named_child(index) end
--- Get the node's child that contains {descendant}.
--- @param descendant TSNode
--- @return TSNode?
+--- @deprecated
function TSNode:child_containing_descendant(descendant) end
+--- Get the node's child that contains {descendant} (includes {descendant}).
+---
+--- For example, with the following node hierarchy:
+---
+--- ```
+--- a -> b -> c
+---
+--- a:child_with_descendant(c) == b
+--- a:child_with_descendant(b) == b
+--- a:child_with_descendant(a) == nil
+--- ```
+--- @param descendant TSNode
+--- @return TSNode?
+function TSNode:child_with_descendant(descendant) end
+
--- Get the node's start position. Return three values: the row, column and
--- total byte count (all zero-based).
--- @return integer, integer, integer
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
index c5e4b86e1e..a825505378 100644
--- a/runtime/lua/vim/treesitter/_query_linter.lua
+++ b/runtime/lua/vim/treesitter/_query_linter.lua
@@ -240,8 +240,12 @@ function M.omnifunc(findstart, base)
table.insert(items, text)
end
end
- for _, s in pairs(parser_info.symbols) do
- local text = s[2] and s[1] or string.format('%q', s[1]):gsub('\n', 'n') ---@type string
+ for text, named in
+ pairs(parser_info.symbols --[[@as table<string, boolean>]])
+ do
+ if not named then
+ text = string.format('%q', text:sub(2, -2)):gsub('\n', 'n') ---@type string
+ end
if text:find(base, 1, true) then
table.insert(items, text)
end
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index 90c3720b80..26817cdba5 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -330,9 +330,7 @@ end
---
--- @param opts vim.treesitter.dev.inspect_tree.Opts?
function M.inspect_tree(opts)
- vim.validate({
- opts = { opts, 't', true },
- })
+ vim.validate('opts', opts, 'table', true)
opts = opts or {}
@@ -529,15 +527,22 @@ function M.inspect_tree(opts)
end,
})
- api.nvim_create_autocmd('BufHidden', {
+ api.nvim_create_autocmd({ 'BufHidden', 'BufUnload', 'QuitPre' }, {
group = group,
buffer = buf,
- once = true,
callback = function()
+ -- don't close inpector window if source buffer
+ -- has more than one open window
+ if #vim.fn.win_findbuf(buf) > 1 then
+ return
+ end
+
-- close all tree windows
for _, window in pairs(vim.fn.win_findbuf(b)) do
close_win(window)
end
+
+ return true
end,
})
end
@@ -667,10 +672,10 @@ function M.edit_query(lang)
api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1)
end,
})
- api.nvim_create_autocmd('BufHidden', {
+ api.nvim_create_autocmd({ 'BufHidden', 'BufUnload' }, {
group = group,
buffer = buf,
- desc = 'Close the editor window when the source buffer is hidden',
+ desc = 'Close the editor window when the source buffer is hidden or unloaded',
once = true,
callback = function()
close_win(query_win)
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index 637f9ea543..53b64d1dec 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -4,10 +4,21 @@ local health = vim.health
--- Performs a healthcheck for treesitter integration
function M.check()
- local parsers = vim.api.nvim_get_runtime_file('parser/*', true)
+ health.start('Treesitter features')
+
+ health.info(
+ string.format(
+ 'Treesitter ABI support: min %d, max %d',
+ vim.treesitter.minimum_language_version,
+ ts.language_version
+ )
+ )
- health.info(string.format('Nvim runtime ABI version: %d', ts.language_version))
+ local can_wasm = vim._ts_add_language_from_wasm ~= nil
+ health.info(string.format('WASM parser support: %s', tostring(can_wasm)))
+ health.start('Treesitter parsers')
+ local parsers = vim.api.nvim_get_runtime_file('parser/*', true)
for _, parser in pairs(parsers) do
local parsername = vim.fn.fnamemodify(parser, ':t:r')
local is_loadable, err_or_nil = pcall(ts.language.add, parsername)
@@ -28,9 +39,6 @@ function M.check()
)
end
end
-
- local can_wasm = vim._ts_add_language_from_wasm ~= nil
- health.info(string.format('Can load WASM parsers: %s', tostring(can_wasm)))
end
return M
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index a94c408f4e..8ce8652f7d 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -93,9 +93,6 @@ function TSHighlighter.new(tree, opts)
opts = opts or {} ---@type { queries: table<string,string> }
self.tree = tree
tree:register_cbs({
- on_bytes = function(...)
- self:on_bytes(...)
- end,
on_detach = function()
self:on_detach()
end,
@@ -215,13 +212,6 @@ function TSHighlighter:for_each_highlight_state(fn)
end
---@package
----@param start_row integer
----@param new_end integer
-function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
- api.nvim__redraw({ buf = self.bufnr, range = { start_row, start_row + new_end + 1 } })
-end
-
----@package
function TSHighlighter:on_detach()
self:destroy()
end
@@ -230,7 +220,7 @@ end
---@param changes Range6[]
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes) do
- api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 } })
+ api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 }, flush = false })
end
end
@@ -328,7 +318,7 @@ local function on_line_impl(self, buf, line, is_spell_nav)
-- The "priority" attribute can be set at the pattern level or on a particular capture
local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
- or vim.highlight.priorities.treesitter
+ or vim.hl.priorities.treesitter
) + spell_pri_offset
-- The "conceal" attribute can be set at the pattern level or on a particular capture
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 9f7807e036..446051dfd7 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -108,11 +108,9 @@ function M.add(lang, opts)
local path = opts.path
local symbol_name = opts.symbol_name
- vim.validate({
- lang = { lang, 'string' },
- path = { path, 'string', true },
- symbol_name = { symbol_name, 'string', true },
- })
+ vim.validate('lang', lang, 'string')
+ vim.validate('path', path, 'string', true)
+ vim.validate('symbol_name', symbol_name, 'string', true)
-- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems)
lang = lang:lower()
@@ -156,10 +154,8 @@ end
--- @param lang string Name of parser
--- @param filetype string|string[] Filetype(s) to associate with lang
function M.register(lang, filetype)
- vim.validate({
- lang = { lang, 'string' },
- filetype = { filetype, { 'string', 'table' } },
- })
+ vim.validate('lang', lang, 'string')
+ vim.validate('filetype', filetype, { 'string', 'table' })
for _, f in ipairs(ensure_list(filetype)) do
if f ~= '' then
@@ -170,7 +166,12 @@ end
--- Inspects the provided language.
---
---- Inspecting provides some useful information on the language like node names, ...
+--- Inspecting provides some useful information on the language like node and field names, ABI
+--- version, and whether the language came from a WASM module.
+---
+--- Node names are returned in a table mapping each node name to a `boolean` indicating whether or
+--- not the node is named (i.e., not anonymous). Anonymous nodes are surrounded with double quotes
+--- (`"`).
---
---@param lang string Language
---@return table
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index fd68c2b910..4b42164dc8 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1037,7 +1037,7 @@ end
--- Registers callbacks for the [LanguageTree].
---@param cbs table<TSCallbackNameOn,function> An [nvim_buf_attach()]-like table argument with the following handlers:
---- - `on_bytes` : see [nvim_buf_attach()], but this will be called _after_ the parsers callback.
+--- - `on_bytes` : see [nvim_buf_attach()].
--- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
--- It will be passed two arguments: a table of the ranges (as node ranges) that
--- changed and the changed tree.
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 4614967799..1677e8d364 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -626,7 +626,7 @@ local directive_handlers = {
--- Adds a new predicate to be used in queries
---
---@param name string Name of the predicate, without leading #
----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata)
+---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata): boolean?
--- - see |vim.treesitter.query.add_directive()| for argument meanings
---@param opts? vim.treesitter.query.add_predicate.Opts
function M.add_predicate(name, handler, opts)
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 532decf5e9..cd159f0172 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -20,7 +20,8 @@ local M = {}
--- end)
--- ```
---
----@param items any[] Arbitrary items
+---@generic T
+---@param items T[] Arbitrary items
---@param opts table Additional options
--- - prompt (string|nil)
--- Text of the prompt. Defaults to `Select one of:`
@@ -32,19 +33,19 @@ local M = {}
--- Plugins reimplementing `vim.ui.select` may wish to
--- use this to infer the structure or semantics of
--- `items`, or the context in which select() was called.
----@param on_choice fun(item: any|nil, idx: integer|nil)
+---@param on_choice fun(item: T|nil, idx: integer|nil)
--- Called once the user made a choice.
--- `idx` is the 1-based index of `item` within `items`.
--- `nil` if the user aborted the dialog.
function M.select(items, opts, on_choice)
- vim.validate({
- items = { items, 'table', false },
- on_choice = { on_choice, 'function', false },
- })
+ vim.validate('items', items, 'table')
+ vim.validate('on_choice', on_choice, 'function')
opts = opts or {}
local choices = { opts.prompt or 'Select one of:' }
local format_item = opts.format_item or tostring
- for i, item in ipairs(items) do
+ for i, item in
+ ipairs(items --[[@as any[] ]])
+ do
table.insert(choices, string.format('%d: %s', i, format_item(item)))
end
local choice = vim.fn.inputlist(choices)
@@ -86,10 +87,8 @@ end
--- an empty string if nothing was entered), or
--- `nil` if the user aborted the dialog.
function M.input(opts, on_confirm)
- vim.validate({
- opts = { opts, 'table', true },
- on_confirm = { on_confirm, 'function', false },
- })
+ vim.validate('opts', opts, 'table', true)
+ vim.validate('on_confirm', on_confirm, 'function')
opts = (opts and not vim.tbl_isempty(opts)) and opts or vim.empty_dict()
@@ -135,9 +134,7 @@ end
---
---@see |vim.system()|
function M.open(path, opt)
- vim.validate({
- path = { path, 'string' },
- })
+ vim.validate('path', path, 'string')
local is_uri = path:match('%w+:')
if not is_uri then
path = vim.fs.normalize(path)
@@ -165,8 +162,10 @@ function M.open(path, opt)
cmd = { 'wslview', path }
elseif vim.fn.executable('explorer.exe') == 1 then
cmd = { 'explorer.exe', path }
+ elseif vim.fn.executable('lemonade') == 1 then
+ cmd = { 'lemonade', 'open', path }
else
- return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)'
+ return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open, lemonade)'
end
return vim.system(cmd, job_opt), nil
@@ -207,7 +206,9 @@ function M._get_urls()
if vim.treesitter.node_contains(node, range) then
local url = metadata[id] and metadata[id].url
if url and match[url] then
- for _, n in ipairs(match[url]) do
+ for _, n in
+ ipairs(match[url] --[[@as TSNode[] ]])
+ do
urls[#urls + 1] =
vim.treesitter.get_node_text(n, bufnr, { metadata = metadata[url] })
end