aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/man.lua3
-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
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