aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/vim/_defaults.lua49
-rw-r--r--runtime/lua/vim/_meta/options.lua1
-rw-r--r--runtime/lua/vim/filetype.lua2
-rw-r--r--runtime/lua/vim/fs.lua12
-rw-r--r--runtime/lua/vim/loader.lua65
-rw-r--r--runtime/lua/vim/lsp.lua34
-rw-r--r--runtime/lua/vim/lsp/_folding_range.lua371
-rw-r--r--runtime/lua/vim/lsp/buf.lua4
-rw-r--r--runtime/lua/vim/lsp/client.lua4
-rw-r--r--runtime/lua/vim/lsp/protocol.lua7
-rw-r--r--runtime/lua/vim/lsp/util.lua6
-rw-r--r--runtime/lua/vim/shared.lua45
-rw-r--r--runtime/lua/vim/text.lua24
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua53
14 files changed, 567 insertions, 110 deletions
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 2687f34302..6583cf48b3 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -546,8 +546,9 @@ do
---
--- @param option string Option name
--- @param value any Option value
- local function setoption(option, value)
- if vim.api.nvim_get_option_info2(option, {}).was_set then
+ --- @param force boolean? Always set the value, even if already set
+ local function setoption(option, value, force)
+ if not force and vim.api.nvim_get_option_info2(option, {}).was_set then
-- Don't do anything if option is already set
return
end
@@ -563,7 +564,7 @@ do
once = true,
nested = true,
callback = function()
- setoption(option, value)
+ setoption(option, value, force)
end,
})
end
@@ -645,11 +646,15 @@ do
return nil, nil, nil
end
- local timer = assert(vim.uv.new_timer())
-
+ -- This autocommand updates the value of 'background' anytime we receive
+ -- an OSC 11 response from the terminal emulator. If the user has set
+ -- 'background' explictly then we will delete this autocommand,
+ -- effectively disabling automatic background setting.
+ local force = false
local id = vim.api.nvim_create_autocmd('TermResponse', {
group = group,
nested = true,
+ desc = "Update the value of 'background' automatically based on the terminal emulator's background color",
callback = function(args)
local resp = args.data ---@type string
local r, g, b = parseosc11(resp)
@@ -661,27 +666,33 @@ do
if rr and gg and bb then
local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
local bg = luminance < 0.5 and 'dark' or 'light'
- setoption('background', bg)
+ setoption('background', bg, force)
+
+ -- On the first query response, don't force setting the option in
+ -- case the user has already set it manually. If they have, then
+ -- this autocommand will be deleted. If they haven't, then we do
+ -- want to force setting the option to override the value set by
+ -- this autocommand.
+ if not force then
+ force = true
+ end
end
+ end
+ end,
+ })
- return true
+ vim.api.nvim_create_autocmd('VimEnter', {
+ group = group,
+ nested = true,
+ once = true,
+ callback = function()
+ if vim.api.nvim_get_option_info2('background', {}).was_set then
+ vim.api.nvim_del_autocmd(id)
end
end,
})
io.stdout:write('\027]11;?\007')
-
- timer:start(1000, 0, function()
- -- Delete the autocommand if no response was received
- vim.schedule(function()
- -- Suppress error if autocommand has already been deleted
- pcall(vim.api.nvim_del_autocmd, id)
- end)
-
- if not timer:is_closing() then
- timer:close()
- end
- end)
end
--- If the TUI (term_has_truecolor) was able to determine that the host
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index c635d9bd3b..247b464a70 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -6311,6 +6311,7 @@ vim.wo.stc = vim.wo.statuscolumn
--- All fields except the {item} are optional. A single percent sign can
--- be given as "%%".
---
+--- *stl-%!*
--- When the option starts with "%!" then it is used as an expression,
--- evaluated and the result is used as the option value. Example:
---
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index aa566973b6..5d771c30e9 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -962,7 +962,9 @@ local extension = {
purs = 'purescript',
arr = 'pyret',
pxd = 'pyrex',
+ pxi = 'pyrex',
pyx = 'pyrex',
+ ['pyx+'] = 'pyrex',
pyw = 'python',
py = 'python',
pyi = 'python',
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index d91eeaf02f..2f007d97c3 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -1,3 +1,15 @@
+--- @brief <pre>help
+--- *vim.fs.exists()*
+--- Use |uv.fs_stat()| to check a file's type, and whether it exists.
+---
+--- Example:
+---
+--- >lua
+--- if vim.uv.fs_stat(file) then
+--- vim.print("file exists")
+--- end
+--- <
+
local uv = vim.uv
local M = {}
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index 0cce0ab21d..71d0188128 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -399,50 +399,51 @@ function M.reset(path)
end
end
---- Enables the experimental Lua module loader:
---- * overrides loadfile
+--- Enables or disables the experimental Lua module loader:
+---
+--- Enable (`enable=true`):
+--- * overrides |loadfile()|
--- * adds the Lua loader using the byte-compilation cache
--- * adds the libs loader
--- * removes the default Nvim loader
---
---- @since 0
-function M.enable()
- if M.enabled then
- return
- end
- M.enabled = true
- vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p')
- _G.loadfile = loadfile_cached
- -- add Lua loader
- table.insert(loaders, 2, loader_cached)
- -- add libs loader
- table.insert(loaders, 3, loader_lib_cached)
- -- remove Nvim loader
- for l, loader in ipairs(loaders) do
- if loader == vim._load_package then
- table.remove(loaders, l)
- break
- end
- end
-end
-
---- Disables the experimental Lua module loader:
+--- Disable (`enable=false`):
--- * removes the loaders
--- * adds the default Nvim loader
---
--- @since 0
-function M.disable()
- if not M.enabled then
+---
+--- @param enable? (boolean) true/nil to enable, false to disable
+function M.enable(enable)
+ enable = enable == nil and true or enable
+ if enable == M.enabled then
return
end
- M.enabled = false
- _G.loadfile = _loadfile
- for l, loader in ipairs(loaders) do
- if loader == loader_cached or loader == loader_lib_cached then
- table.remove(loaders, l)
+ M.enabled = enable
+
+ if enable then
+ vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p')
+ _G.loadfile = loadfile_cached
+ -- add Lua loader
+ table.insert(loaders, 2, loader_cached)
+ -- add libs loader
+ table.insert(loaders, 3, loader_lib_cached)
+ -- remove Nvim loader
+ for l, loader in ipairs(loaders) do
+ if loader == vim._load_package then
+ table.remove(loaders, l)
+ break
+ end
+ end
+ else
+ _G.loadfile = _loadfile
+ for l, loader in ipairs(loaders) do
+ if loader == loader_cached or loader == loader_lib_cached then
+ table.remove(loaders, l)
+ end
end
+ table.insert(loaders, 2, vim._load_package)
end
- table.insert(loaders, 2, vim._load_package)
end
--- Tracks the time spent in a function
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 6d29c9e4df..a3791e15c3 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -3,6 +3,7 @@ local validate = vim.validate
local lsp = vim._defer_require('vim.lsp', {
_changetracking = ..., --- @module 'vim.lsp._changetracking'
+ _folding_range = ..., --- @module 'vim.lsp._folding_range'
_snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
_tagfunc = ..., --- @module 'vim.lsp._tagfunc'
_watchfiles = ..., --- @module 'vim.lsp._watchfiles'
@@ -57,6 +58,7 @@ lsp._request_name_to_capability = {
[ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
[ms.textDocument_documentLink] = { 'documentLinkProvider' },
[ms.textDocument_documentSymbol] = { 'documentSymbolProvider' },
+ [ms.textDocument_foldingRange] = { 'foldingRangeProvider' },
[ms.textDocument_formatting] = { 'documentFormattingProvider' },
[ms.textDocument_hover] = { 'hoverProvider' },
[ms.textDocument_implementation] = { 'implementationProvider' },
@@ -1094,6 +1096,38 @@ function lsp.tagfunc(pattern, flags)
return vim.lsp._tagfunc(pattern, flags)
end
+--- Provides an interface between the built-in client and a `foldexpr` function.
+---@param lnum integer line number
+function lsp.foldexpr(lnum)
+ return vim.lsp._folding_range.foldexpr(lnum)
+end
+
+--- Close all {kind} of folds in the the window with {winid}.
+---
+--- To automatically fold imports when opening a file, you can use an autocmd:
+---
+--- ```lua
+--- vim.api.nvim_create_autocmd('LspNotify', {
+--- callback = function(args)
+--- if args.data.method == 'textDocument/didOpen' then
+--- vim.lsp.foldclose('imports', vim.fn.bufwinid(args.buf))
+--- end
+--- end,
+--- })
+--- ```
+---
+---@param kind lsp.FoldingRangeKind Kind to close, one of "comment", "imports" or "region".
+---@param winid? integer Defaults to the current window.
+function lsp.foldclose(kind, winid)
+ return vim.lsp._folding_range.foldclose(kind, winid)
+end
+
+--- Provides a `foldtext` function that shows the `collapsedText` retrieved,
+--- defaults to the first folded line if `collapsedText` is not provided.
+function lsp.foldtext()
+ return vim.lsp._folding_range.foldtext()
+end
+
---Checks whether a client is stopped.
---
---@param client_id (integer)
diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua
new file mode 100644
index 0000000000..6a445017a3
--- /dev/null
+++ b/runtime/lua/vim/lsp/_folding_range.lua
@@ -0,0 +1,371 @@
+local util = require('vim.lsp.util')
+local log = require('vim.lsp.log')
+local ms = require('vim.lsp.protocol').Methods
+local api = vim.api
+
+local M = {}
+
+---@class (private) vim.lsp.folding_range.BufState
+---
+---@field version? integer
+---
+--- Never use this directly, `renew()` the cached foldinfo
+--- then use on demand via `row_*` fields.
+---
+--- Index In the form of client_id -> ranges
+---@field client_ranges table<integer, lsp.FoldingRange[]?>
+---
+--- Index in the form of row -> [foldlevel, mark]
+---@field row_level table<integer, [integer, ">" | "<"?]?>
+---
+--- Index in the form of start_row -> kinds
+---@field row_kinds table<integer, table<lsp.FoldingRangeKind, true?>?>>
+---
+--- Index in the form of start_row -> collapsed_text
+---@field row_text table<integer, string?>
+
+---@type table<integer, vim.lsp.folding_range.BufState?>
+local bufstates = {}
+
+--- Renew the cached foldinfo in the buffer.
+---@param bufnr integer
+local function renew(bufnr)
+ local bufstate = assert(bufstates[bufnr])
+
+ ---@type table<integer, [integer, ">" | "<"?]?>
+ local row_level = {}
+ ---@type table<integer, table<lsp.FoldingRangeKind, true?>?>>
+ local row_kinds = {}
+ ---@type table<integer, string?>
+ local row_text = {}
+
+ for _, ranges in pairs(bufstate.client_ranges) do
+ for _, range in ipairs(ranges) do
+ local start_row = range.startLine
+ local end_row = range.endLine
+ -- Adding folds within a single line is not supported by Nvim.
+ if start_row ~= end_row then
+ row_text[start_row] = range.collapsedText
+
+ local kind = range.kind
+ if kind then
+ local kinds = row_kinds[start_row] or {}
+ kinds[kind] = true
+ row_kinds[start_row] = kinds
+ end
+
+ for row = start_row, end_row do
+ local level = row_level[row] or { 0 }
+ level[1] = level[1] + 1
+ row_level[row] = level
+ end
+ row_level[start_row][2] = '>'
+ row_level[end_row][2] = '<'
+ end
+ end
+ end
+
+ bufstate.row_level = row_level
+ bufstate.row_kinds = row_kinds
+ bufstate.row_text = row_text
+end
+
+--- Renew the cached foldinfo then force `foldexpr()` to be re-evaluated,
+--- without opening folds.
+---@param bufnr integer
+local function foldupdate(bufnr)
+ renew(bufnr)
+ for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do
+ local wininfo = vim.fn.getwininfo(winid)[1]
+ if wininfo and wininfo.tabnr == vim.fn.tabpagenr() then
+ if vim.wo[winid].foldmethod == 'expr' then
+ vim._foldupdate(winid, 0, api.nvim_buf_line_count(bufnr))
+ end
+ end
+ end
+end
+
+--- Whether `foldupdate()` is scheduled for the buffer with `bufnr`.
+---
+--- Index in the form of bufnr -> true?
+---@type table<integer, true?>
+local scheduled_foldupdate = {}
+
+--- Schedule `foldupdate()` after leaving insert mode.
+---@param bufnr integer
+local function schedule_foldupdate(bufnr)
+ if not scheduled_foldupdate[bufnr] then
+ scheduled_foldupdate[bufnr] = true
+ api.nvim_create_autocmd('InsertLeave', {
+ buffer = bufnr,
+ once = true,
+ callback = function()
+ foldupdate(bufnr)
+ scheduled_foldupdate[bufnr] = nil
+ end,
+ })
+ end
+end
+
+---@param results table<integer,{err: lsp.ResponseError?, result: lsp.FoldingRange[]?}>
+---@type lsp.MultiHandler
+local function multi_handler(results, ctx)
+ local bufnr = assert(ctx.bufnr)
+ -- Handling responses from outdated buffer only causes performance overhead.
+ if util.buf_versions[bufnr] ~= ctx.version then
+ return
+ end
+
+ local bufstate = assert(bufstates[bufnr])
+ for client_id, result in pairs(results) do
+ if result.err then
+ log.error(result.err)
+ else
+ bufstate.client_ranges[client_id] = result.result
+ end
+ end
+ bufstate.version = ctx.version
+
+ if api.nvim_get_mode().mode:match('^i') then
+ -- `foldUpdate()` is guarded in insert mode.
+ schedule_foldupdate(bufnr)
+ else
+ foldupdate(bufnr)
+ end
+end
+
+---@param result lsp.FoldingRange[]?
+---@type lsp.Handler
+local function handler(err, result, ctx)
+ multi_handler({ [ctx.client_id] = { err = err, result = result } }, ctx)
+end
+
+--- Request `textDocument/foldingRange` from the server.
+--- `foldupdate()` is scheduled once after the request is completed.
+---@param bufnr integer
+---@param client? vim.lsp.Client The client whose server supports `foldingRange`.
+local function request(bufnr, client)
+ ---@type lsp.FoldingRangeParams
+ local params = { textDocument = util.make_text_document_params(bufnr) }
+
+ if client then
+ client:request(ms.textDocument_foldingRange, params, handler, bufnr)
+ return
+ end
+
+ if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
+ return
+ end
+
+ vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, multi_handler)
+end
+
+-- NOTE:
+-- `bufstate` and event hooks are interdependent:
+-- * `bufstate` needs event hooks for correctness.
+-- * event hooks require the previous `bufstate` for updates.
+-- Since they are manually created and destroyed,
+-- we ensure their lifecycles are always synchronized.
+--
+-- TODO(ofseed):
+-- 1. Implement clearing `bufstate` and event hooks
+-- when no clients in the buffer support the corresponding method.
+-- 2. Then generalize this state management to other LSP modules.
+local augroup_setup = api.nvim_create_augroup('vim_lsp_folding_range/setup', {})
+
+--- Initialize `bufstate` and event hooks, then request folding ranges.
+--- Manage their lifecycle within this function.
+---@param bufnr integer
+---@return vim.lsp.folding_range.BufState?
+local function setup(bufnr)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
+ -- Register the new `bufstate`.
+ bufstates[bufnr] = {
+ client_ranges = {},
+ row_level = {},
+ row_kinds = {},
+ row_text = {},
+ }
+
+ -- Event hooks from `buf_attach` can't be removed externally.
+ -- Hooks and `bufstate` share the same lifecycle;
+ -- they should self-destroy if `bufstate == nil`.
+ api.nvim_buf_attach(bufnr, false, {
+ -- `on_detach` also runs on buffer reload (`:e`).
+ -- Ensure `bufstate` and hooks are cleared to avoid duplication or leftover states.
+ on_detach = function()
+ bufstates[bufnr] = nil
+ api.nvim_clear_autocmds({ buffer = bufnr, group = augroup_setup })
+ end,
+ -- Reset `bufstate` and request folding ranges.
+ on_reload = function()
+ bufstates[bufnr] = {
+ client_ranges = {},
+ row_level = {},
+ row_kinds = {},
+ row_text = {},
+ }
+ request(bufnr)
+ end,
+ --- Sync changed rows with their previous foldlevels before applying new ones.
+ on_bytes = function(_, _, _, start_row, _, _, old_row, _, _, new_row, _, _)
+ if bufstates[bufnr] == nil then
+ return true
+ end
+ local row_level = bufstates[bufnr].row_level
+ if next(row_level) == nil then
+ return
+ end
+ local row = new_row - old_row
+ if row > 0 then
+ vim._list_insert(row_level, start_row, start_row + math.abs(row) - 1, { -1 })
+ -- If the previous row ends a fold,
+ -- Nvim treats the first row after consecutive `-1`s as a new fold start,
+ -- which is not the desired behavior.
+ local prev_level = row_level[start_row - 1]
+ if prev_level and prev_level[2] == '<' then
+ row_level[start_row] = { prev_level[1] - 1 }
+ end
+ elseif row < 0 then
+ vim._list_remove(row_level, start_row, start_row + math.abs(row) - 1)
+ end
+ end,
+ })
+ api.nvim_create_autocmd('LspDetach', {
+ group = augroup_setup,
+ buffer = bufnr,
+ callback = function(args)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
+ ---@type integer
+ local client_id = args.data.client_id
+ bufstates[bufnr].client_ranges[client_id] = nil
+
+ ---@type vim.lsp.Client[]
+ local clients = vim
+ .iter(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange }))
+ ---@param client vim.lsp.Client
+ :filter(function(client)
+ return client.id ~= client_id
+ end)
+ :totable()
+ if #clients == 0 then
+ bufstates[bufnr] = {
+ client_ranges = {},
+ row_level = {},
+ row_kinds = {},
+ row_text = {},
+ }
+ end
+
+ foldupdate(bufnr)
+ end,
+ })
+ api.nvim_create_autocmd('LspAttach', {
+ group = augroup_setup,
+ buffer = bufnr,
+ callback = function(args)
+ local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
+ request(bufnr, client)
+ end,
+ })
+ api.nvim_create_autocmd('LspNotify', {
+ group = augroup_setup,
+ buffer = bufnr,
+ callback = function(args)
+ local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
+ if
+ client:supports_method(ms.textDocument_foldingRange, bufnr)
+ and (
+ args.data.method == ms.textDocument_didChange
+ or args.data.method == ms.textDocument_didOpen
+ )
+ then
+ request(bufnr, client)
+ end
+ end,
+ })
+
+ request(bufnr)
+
+ return bufstates[bufnr]
+end
+
+---@param kind lsp.FoldingRangeKind
+---@param winid integer
+local function foldclose(kind, winid)
+ vim._with({ win = winid }, function()
+ local bufnr = api.nvim_win_get_buf(winid)
+ local row_kinds = bufstates[bufnr].row_kinds
+ -- Reverse traverse to ensure that the smallest ranges are closed first.
+ for row = api.nvim_buf_line_count(bufnr) - 1, 0, -1 do
+ local kinds = row_kinds[row]
+ if kinds and kinds[kind] then
+ vim.cmd(row + 1 .. 'foldclose')
+ end
+ end
+ end)
+end
+
+---@param kind lsp.FoldingRangeKind
+---@param winid? integer
+function M.foldclose(kind, winid)
+ vim.validate('kind', kind, 'string')
+ vim.validate('winid', winid, 'number', true)
+
+ winid = winid or api.nvim_get_current_win()
+ local bufnr = api.nvim_win_get_buf(winid)
+ local bufstate = bufstates[bufnr]
+ if not bufstate then
+ return
+ end
+
+ if bufstate.version == util.buf_versions[bufnr] then
+ foldclose(kind, winid)
+ return
+ end
+ -- Schedule `foldclose()` if the buffer is not up-to-date.
+
+ if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
+ return
+ end
+ ---@type lsp.FoldingRangeParams
+ local params = { textDocument = util.make_text_document_params(bufnr) }
+ vim.lsp.buf_request_all(bufnr, ms.textDocument_foldingRange, params, function(...)
+ multi_handler(...)
+ foldclose(kind, winid)
+ end)
+end
+
+---@return string
+function M.foldtext()
+ local bufnr = api.nvim_get_current_buf()
+ local lnum = vim.v.foldstart
+ local row = lnum - 1
+ local bufstate = bufstates[bufnr]
+ if bufstate and bufstate.row_text[row] then
+ return bufstate.row_text[row]
+ end
+ return vim.fn.getline(lnum)
+end
+
+---@param lnum? integer
+---@return string level
+function M.foldexpr(lnum)
+ local bufnr = api.nvim_get_current_buf()
+ local bufstate = bufstates[bufnr] or setup(bufnr)
+ if not bufstate then
+ return '0'
+ end
+
+ local row = (lnum or vim.v.lnum) - 1
+ local level = bufstate.row_level[row]
+ return level and (level[2] or '') .. (level[1] or '0') or '0'
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index aca6bf27f4..10479807a2 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -487,7 +487,7 @@ end
--- ```lua
--- -- Never request typescript-language-server for formatting
--- vim.lsp.buf.format {
---- filter = function(client) return client.name ~= "tsserver" end
+--- filter = function(client) return client.name ~= "ts_ls" end
--- }
--- ```
--- @field filter? fun(client: vim.lsp.Client): boolean?
@@ -736,7 +736,7 @@ end
--- Lists all the references to the symbol under the cursor in the quickfix window.
---
----@param context (table|nil) Context for the request
+---@param context lsp.ReferenceContext? Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
---@param opts? vim.lsp.ListOpts
function M.references(context, opts)
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 7c2b7192f5..a14b6ccda6 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -639,7 +639,7 @@ end
--- @param method string LSP method name.
--- @param params? table LSP request params.
--- @param handler? lsp.Handler Response |lsp-handler| for this method.
---- @param bufnr? integer Buffer handle. 0 for current (default).
+--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current.
--- @return boolean status indicates whether the request was successful.
--- If it is `false`, then it will always be `false` (the client has shutdown).
--- @return integer? request_id Can be used with |Client:cancel_request()|.
@@ -718,7 +718,7 @@ end
--- @param params table LSP request params.
--- @param timeout_ms integer? Maximum time in milliseconds to wait for
--- a result. Defaults to 1000
---- @param bufnr integer Buffer handle (0 for current).
+--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current.
--- @return {err: lsp.ResponseError?, result:any}? `result` and `err` from the |lsp-handler|.
--- `nil` is the request was unsuccessful
--- @return string? err On timeout, cancel or error, where `err` is a
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 3d29dad90a..cfd47d8f7c 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -440,6 +440,13 @@ function protocol.make_client_capabilities()
properties = { 'command' },
},
},
+ foldingRange = {
+ dynamicRegistration = false,
+ lineFoldingOnly = true,
+ foldingRange = {
+ collapsedText = true,
+ },
+ },
formatting = {
dynamicRegistration = true,
},
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 3de76f93a6..ced8aa5745 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1894,7 +1894,7 @@ function M.make_position_params(window, position_encoding)
local buf = api.nvim_win_get_buf(window)
if position_encoding == nil then
vim.notify_once(
- 'warning: position_encoding is required, using the offset_encoding from the first client',
+ 'position_encoding param is required in vim.lsp.util.make_position_params. Defaulting to position encoding of the first client.',
vim.log.levels.WARN
)
position_encoding = M._get_offset_encoding(buf)
@@ -1950,7 +1950,7 @@ function M.make_range_params(window, position_encoding)
local buf = api.nvim_win_get_buf(window or 0)
if position_encoding == nil then
vim.notify_once(
- 'warning: position_encoding is required, using the offset_encoding from the first client',
+ 'position_encoding param is required in vim.lsp.util.make_range_params. Defaulting to position encoding of the first client.',
vim.log.levels.WARN
)
position_encoding = M._get_offset_encoding(buf)
@@ -1979,7 +1979,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding)
bufnr = bufnr or api.nvim_get_current_buf()
if position_encoding == nil then
vim.notify_once(
- 'warning: position_encoding is required, using the offset_encoding from the first client',
+ 'position_encoding param is required in vim.lsp.util.make_given_range_params. Defaulting to position encoding of the first client.',
vim.log.levels.WARN
)
position_encoding = M._get_offset_encoding(bufnr)
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 4f2373b182..2e8edea22a 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -737,6 +737,51 @@ function vim.list_slice(list, start, finish)
return new_list
end
+--- Efficiently insert items into the middle of a list.
+---
+--- Calling table.insert() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+---@param v any
+function vim._list_insert(t, first, last, v)
+ local n = #t
+
+ -- Shift table forward
+ for i = n - first, 0, -1 do
+ t[last + 1 + i] = t[first + i]
+ end
+
+ -- Fill in new values
+ for i = first, last do
+ t[i] = v
+ end
+end
+
+--- Efficiently remove items from middle of a list.
+---
+--- Calling table.remove() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+function vim._list_remove(t, first, last)
+ local n = #t
+ for i = 0, n - first do
+ t[first + i] = t[last + 1 + i]
+ t[last + 1 + i] = nil
+ end
+end
+
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
---@see |lua-patterns|
diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua
index d45c8021c6..f910ab3a1d 100644
--- a/runtime/lua/vim/text.lua
+++ b/runtime/lua/vim/text.lua
@@ -2,6 +2,18 @@
local M = {}
+local alphabet = '0123456789ABCDEF'
+local atoi = {} ---@type table<string, integer>
+local itoa = {} ---@type table<integer, string>
+do
+ for i = 1, #alphabet do
+ local char = alphabet:sub(i, i)
+ itoa[i - 1] = char
+ atoi[char] = i - 1
+ atoi[char:lower()] = i - 1
+ end
+end
+
--- Hex encode a string.
---
--- @param str string String to encode
@@ -9,7 +21,9 @@ local M = {}
function M.hexencode(str)
local enc = {} ---@type string[]
for i = 1, #str do
- enc[i] = string.format('%02X', str:byte(i, i + 1))
+ local byte = str:byte(i)
+ enc[2 * i - 1] = itoa[math.floor(byte / 16)]
+ enc[2 * i] = itoa[byte % 16]
end
return table.concat(enc)
end
@@ -26,8 +40,12 @@ function M.hexdecode(enc)
local str = {} ---@type string[]
for i = 1, #enc, 2 do
- local n = assert(tonumber(enc:sub(i, i + 1), 16))
- str[#str + 1] = string.char(n)
+ local u = atoi[enc:sub(i, i)]
+ local l = atoi[enc:sub(i + 1, i + 1)]
+ if not u or not l then
+ return nil, 'string must contain only hex characters'
+ end
+ str[(i + 1) / 2] = string.char(u * 16 + l)
end
return table.concat(str), nil
end
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 7237d2e7d4..0cb5b497c7 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -30,65 +30,20 @@ function FoldInfo.new()
}, FoldInfo)
end
---- Efficiently remove items from middle of a list a list.
----
---- Calling table.remove() in a loop will re-index the tail of the table on
---- every iteration, instead this function will re-index the table exactly
---- once.
----
---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
----
----@param t any[]
----@param first integer
----@param last integer
-local function list_remove(t, first, last)
- local n = #t
- for i = 0, n - first do
- t[first + i] = t[last + 1 + i]
- t[last + 1 + i] = nil
- end
-end
-
---@package
---@param srow integer
---@param erow integer 0-indexed, exclusive
function FoldInfo:remove_range(srow, erow)
- list_remove(self.levels, srow + 1, erow)
- list_remove(self.levels0, srow + 1, erow)
-end
-
---- Efficiently insert items into the middle of a list.
----
---- Calling table.insert() in a loop will re-index the tail of the table on
---- every iteration, instead this function will re-index the table exactly
---- once.
----
---- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
----
----@param t any[]
----@param first integer
----@param last integer
----@param v any
-local function list_insert(t, first, last, v)
- local n = #t
-
- -- Shift table forward
- for i = n - first, 0, -1 do
- t[last + 1 + i] = t[first + i]
- end
-
- -- Fill in new values
- for i = first, last do
- t[i] = v
- end
+ vim._list_remove(self.levels, srow + 1, erow)
+ vim._list_remove(self.levels0, srow + 1, erow)
end
---@package
---@param srow integer
---@param erow integer 0-indexed, exclusive
function FoldInfo:add_range(srow, erow)
- list_insert(self.levels, srow + 1, erow, -1)
- list_insert(self.levels0, srow + 1, erow, -1)
+ vim._list_insert(self.levels, srow + 1, erow, -1)
+ vim._list_insert(self.levels0, srow + 1, erow, -1)
end
---@param range Range2