diff options
Diffstat (limited to 'runtime/lua')
57 files changed, 3051 insertions, 2766 deletions
diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index fce8f89be8..114c94f9e5 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -305,7 +305,7 @@ local function matchstr(text, pat_or_re) return end - return text:sub(vim.str_utfindex(text, s) + 1, vim.str_utfindex(text, e)) + return text:sub(vim.str_utfindex(text, 'utf-32', s) + 1, vim.str_utfindex(text, 'utf-32', e)) end -- attempt to extract the name and sect out of 'name(sect)' @@ -675,7 +675,6 @@ function M.init_pager() vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } }) end - vim.g.pager = true set_options() end 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 |