diff options
Diffstat (limited to 'runtime/lua')
-rw-r--r-- | runtime/lua/vim/_defaults.lua | 49 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/fs.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/loader.lua | 65 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 34 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_folding_range.lua | 371 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/client.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 7 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 45 | ||||
-rw-r--r-- | runtime/lua/vim/text.lua | 24 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 53 |
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 |