neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

editor.lua (41201B)


      1 -- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib)
      2 --
      3 
      4 -- These are for loading runtime modules lazily since they aren't available in
      5 -- the nvim binary as specified in executor.c
      6 for k, v in pairs({
      7  treesitter = true,
      8  filetype = true,
      9  loader = true,
     10  func = true,
     11  F = true,
     12  lsp = true,
     13  hl = true,
     14  diagnostic = true,
     15  keymap = true,
     16  ui = true,
     17  health = true,
     18  secure = true,
     19  snippet = true,
     20  pack = true,
     21  _watch = true,
     22  net = true,
     23  pos = true,
     24  range = true,
     25 }) do
     26  vim._submodules[k] = v
     27 end
     28 
     29 -- There are things which have special rules in vim._init_packages
     30 -- for legacy reasons (uri) or for performance (_inspector).
     31 -- most new things should go into a submodule namespace ( vim.foobar.do_thing() )
     32 vim._extra = {
     33  uri_from_fname = true,
     34  uri_from_bufnr = true,
     35  uri_to_fname = true,
     36  uri_to_bufnr = true,
     37  show_pos = true,
     38  inspect_pos = true,
     39 }
     40 
     41 --- @nodoc
     42 vim.log = {
     43  --- @enum vim.log.levels
     44  levels = {
     45    TRACE = 0,
     46    DEBUG = 1,
     47    INFO = 2,
     48    WARN = 3,
     49    ERROR = 4,
     50    OFF = 5,
     51  },
     52 }
     53 
     54 local utfs = {
     55  ['utf-8'] = true,
     56  ['utf-16'] = true,
     57  ['utf-32'] = true,
     58 }
     59 
     60 -- Gets process info from the `ps` command.
     61 -- Used by nvim_get_proc() as a fallback.
     62 function vim._os_proc_info(pid)
     63  if pid == nil or pid <= 0 or type(pid) ~= 'number' then
     64    error('invalid pid')
     65  end
     66  local cmd = { 'ps', '-p', pid, '-o', 'comm=' }
     67  local r = vim.system(cmd):wait()
     68  local name = assert(r.stdout)
     69  if r.code == 1 and vim.trim(name) == '' then
     70    return {} -- Process not found.
     71  elseif r.code ~= 0 then
     72    error('command failed: ' .. vim.fn.string(cmd))
     73  end
     74  local ppid_string = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout)
     75  -- Remove trailing whitespace.
     76  name = vim.trim(name):gsub('^.*/', '')
     77  local ppid = tonumber(ppid_string) or -1
     78  return {
     79    name = name,
     80    pid = pid,
     81    ppid = ppid,
     82  }
     83 end
     84 
     85 -- Gets process children from the `pgrep` command.
     86 -- Used by nvim_get_proc_children() as a fallback.
     87 function vim._os_proc_children(ppid)
     88  if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
     89    error('invalid ppid')
     90  end
     91  local cmd = { 'pgrep', '-P', ppid }
     92  local r = vim.system(cmd):wait()
     93  if r.code == 1 and vim.trim(r.stdout) == '' then
     94    return {} -- Process not found.
     95  elseif r.code ~= 0 then
     96    error('command failed: ' .. vim.fn.string(cmd))
     97  end
     98  local children = {}
     99  for s in r.stdout:gmatch('%S+') do
    100    local i = tonumber(s)
    101    if i ~= nil then
    102      table.insert(children, i)
    103    end
    104  end
    105  return children
    106 end
    107 
    108 --- @nodoc
    109 --- @class vim.inspect.Opts
    110 --- @field depth? integer
    111 --- @field newline? string
    112 --- @field process? fun(item:any, path: string[]): any
    113 
    114 --- Gets a human-readable representation of the given object.
    115 ---
    116 ---@see |vim.print()|
    117 ---@see https://github.com/kikito/inspect.lua
    118 ---@see https://github.com/mpeterv/vinspect
    119 ---@return string
    120 ---@overload fun(x: any, opts?: vim.inspect.Opts): string
    121 vim.inspect = vim.inspect
    122 
    123 do
    124  local startpos, tdots, tick, got_line1, undo_started, trailing_nl = nil, 0, 0, false, false, false
    125 
    126  --- Paste handler, invoked by |nvim_paste()|.
    127  ---
    128  --- Note: This is provided only as a "hook", don't call it directly; call |nvim_paste()| instead,
    129  --- which arranges redo (dot-repeat) and invokes `vim.paste`.
    130  ---
    131  --- Example: To remove ANSI color codes when pasting:
    132  ---
    133  --- ```lua
    134  --- vim.paste = (function(overridden)
    135  ---   return function(lines, phase)
    136  ---     for i,line in ipairs(lines) do
    137  ---       -- Scrub ANSI color codes from paste input.
    138  ---       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
    139  ---     end
    140  ---     return overridden(lines, phase)
    141  ---   end
    142  --- end)(vim.paste)
    143  --- ```
    144  ---
    145  ---@see |paste|
    146  ---
    147  ---@param lines  string[] # |readfile()|-style list of lines to paste. |channel-lines|
    148  ---@param phase (-1|1|2|3)  -1: "non-streaming" paste: the call contains all lines.
    149  ---              If paste is "streamed", `phase` indicates the stream state:
    150  ---                - 1: starts the paste (exactly once)
    151  ---                - 2: continues the paste (zero or more times)
    152  ---                - 3: ends the paste (exactly once)
    153  ---@return boolean result false if client should cancel the paste.
    154  function vim.paste(lines, phase)
    155    local now = vim.uv.now()
    156    local is_first_chunk = phase < 2
    157    local is_last_chunk = phase == -1 or phase == 3
    158    if is_first_chunk then -- Reset flags.
    159      tdots, tick, got_line1, undo_started, trailing_nl = now, 0, false, false, false
    160    end
    161    if #lines == 0 then
    162      lines = { '' }
    163    end
    164    if #lines == 1 and lines[1] == '' and not is_last_chunk then
    165      -- An empty chunk can cause some edge cases in streamed pasting,
    166      -- so don't do anything unless it is the last chunk.
    167      return true
    168    end
    169    -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead.
    170    if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line.
    171      if not got_line1 then
    172        got_line1 = (#lines > 1)
    173        -- Escape control characters
    174        local line1 = lines[1]:gsub('(%c)', '\022%1')
    175        -- nvim_input() is affected by mappings,
    176        -- so use nvim_feedkeys() with "n" flag to ignore mappings.
    177        -- "t" flag is also needed so the pasted text is saved in cmdline history.
    178        vim.api.nvim_feedkeys(line1, 'nt', true)
    179      end
    180      return true
    181    end
    182    local mode = vim.api.nvim_get_mode().mode
    183    if undo_started then
    184      vim.api.nvim_command('undojoin')
    185    end
    186    if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer
    187      vim.api.nvim_put(lines, 'c', false, true)
    188    elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode
    189      -- TODO: implement Replace mode streamed pasting
    190      -- TODO: support Virtual Replace mode
    191      local nchars = 0
    192      for _, line in ipairs(lines) do
    193        nchars = nchars + line:len()
    194      end
    195      --- @type integer, integer
    196      local row, col = unpack(vim.api.nvim_win_get_cursor(0))
    197      local bufline = vim.api.nvim_buf_get_lines(0, row - 1, row, true)[1]
    198      local firstline = lines[1]
    199      firstline = bufline:sub(1, col) .. firstline
    200      lines[1] = firstline
    201      lines[#lines] = lines[#lines] .. bufline:sub(col + nchars + 1, bufline:len())
    202      vim.api.nvim_buf_set_lines(0, row - 1, row, false, lines)
    203    elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode
    204      if mode:find('^n') then -- Normal mode
    205        -- When there was a trailing new line in the previous chunk,
    206        -- the cursor is on the first character of the next line,
    207        -- so paste before the cursor instead of after it.
    208        vim.api.nvim_put(lines, 'c', not trailing_nl, false)
    209      else -- Visual or Select mode
    210        vim.api.nvim_command([[exe "silent normal! \<Del>"]])
    211        local del_start = vim.fn.getpos("'[")
    212        local cursor_pos = vim.fn.getpos('.')
    213        if mode:find('^[VS]') then -- linewise
    214          if cursor_pos[2] < del_start[2] then -- replacing lines at eof
    215            -- create a new line
    216            vim.api.nvim_put({ '' }, 'l', true, true)
    217          end
    218          vim.api.nvim_put(lines, 'c', false, false)
    219        else
    220          -- paste after cursor when replacing text at eol, otherwise paste before cursor
    221          vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false)
    222        end
    223      end
    224      -- put cursor at the end of the text instead of one character after it
    225      vim.fn.setpos('.', vim.fn.getpos("']"))
    226      trailing_nl = lines[#lines] == ''
    227    else -- Don't know what to do in other modes
    228      return false
    229    end
    230    undo_started = true
    231    if not is_last_chunk and (now - tdots >= 100) then
    232      local dots = ('.'):rep(tick % 4)
    233      tdots = now
    234      tick = tick + 1
    235      -- Use :echo because Lua print('') is a no-op, and we want to clear the
    236      -- message when there are zero dots.
    237      vim.api.nvim_command(('echo "%s"'):format(dots))
    238    end
    239    if startpos == nil then
    240      startpos = vim.fn.getpos("'[")
    241    else
    242      vim.fn.setpos("'[", startpos)
    243    end
    244    if is_last_chunk then
    245      startpos = nil
    246      vim.api.nvim_command('redraw' .. (tick > 1 and '|echo ""' or ''))
    247    end
    248    return true -- Paste will not continue if not returning `true`.
    249  end
    250 end
    251 
    252 --- Returns a function which calls {fn} via |vim.schedule()|.
    253 ---
    254 --- The returned function passes all arguments to {fn}.
    255 ---
    256 --- Example:
    257 ---
    258 --- ```lua
    259 --- function notify_readable(_err, readable)
    260 ---   vim.notify("readable? " .. tostring(readable))
    261 --- end
    262 --- vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable))
    263 --- ```
    264 ---
    265 ---@see |lua-loop-callbacks|
    266 ---@see |vim.schedule()|
    267 ---@see |vim.in_fast_event()|
    268 ---@param fn function
    269 ---@return function
    270 function vim.schedule_wrap(fn)
    271  return function(...)
    272    local args = vim.F.pack_len(...)
    273    vim.schedule(function()
    274      fn(vim.F.unpack_len(args))
    275    end)
    276  end
    277 end
    278 
    279 -- vim.fn.{func}(...)
    280 ---@nodoc
    281 vim.fn = setmetatable({}, {
    282  --- @param t table<string,function>
    283  --- @param key string
    284  --- @return function
    285  __index = function(t, key)
    286    local _fn --- @type function
    287    if vim.api[key] ~= nil then
    288      _fn = function()
    289        error(string.format('Tried to call API function with vim.fn: use vim.api.%s instead', key))
    290      end
    291    else
    292      _fn = function(...)
    293        return vim.call(key, ...)
    294      end
    295    end
    296    t[key] = _fn
    297    return _fn
    298  end,
    299 })
    300 
    301 --- @private
    302 vim.funcref = function(viml_func_name)
    303  return vim.fn[viml_func_name]
    304 end
    305 
    306 local VIM_CMD_ARG_MAX = 20
    307 
    308 --- Executes Vimscript (|Ex-commands|).
    309 ---
    310 --- Can be indexed with a command name to get a function, thus you can write `vim.cmd.echo(…)`
    311 --- instead of `vim.cmd{cmd='echo',…}`.
    312 ---
    313 --- Examples:
    314 ---
    315 --- ```lua
    316 --- -- Single command:
    317 --- vim.cmd('echo 42')
    318 --- -- Multiline script:
    319 --- vim.cmd([[
    320 ---   augroup my.group
    321 ---     autocmd!
    322 ---     autocmd FileType c setlocal cindent
    323 ---   augroup END
    324 --- ]])
    325 ---
    326 --- -- Ex command :echo "foo". Note: string literals must be double-quoted.
    327 --- vim.cmd('echo "foo"')
    328 --- vim.cmd { cmd = 'echo', args = { '"foo"' } }
    329 --- vim.cmd.echo({ args = { '"foo"' } })
    330 --- vim.cmd.echo('"foo"')
    331 ---
    332 --- -- Ex command :write! myfile.txt
    333 --- vim.cmd('write! myfile.txt')
    334 --- vim.cmd { cmd = 'write', args = { 'myfile.txt' }, bang = true }
    335 --- vim.cmd.write { args = { 'myfile.txt' }, bang = true }
    336 --- vim.cmd.write { 'myfile.txt', bang = true }
    337 ---
    338 --- -- Ex command :vertical resize +2
    339 --- vim.cmd.resize({ '+2', mods = { vertical = true } })
    340 --- ```
    341 ---
    342 ---@diagnostic disable-next-line: undefined-doc-param
    343 ---@param command string|table Command(s) to execute.
    344 ---       - The string form supports multiline Vimscript (alias to |nvim_exec2()|, behaves
    345 ---         like |:source|).
    346 ---       - The table form executes a single command (alias to |nvim_cmd()|).
    347 ---@see |ex-cmd-index|
    348 vim.cmd = setmetatable({}, {
    349  __call = function(_, command)
    350    if type(command) == 'table' then
    351      return vim.api.nvim_cmd(command, {})
    352    else
    353      vim.api.nvim_exec2(command, {})
    354      return ''
    355    end
    356  end,
    357  --- @param t table<string,function>
    358  __index = function(t, command)
    359    t[command] = function(...)
    360      local opts --- @type vim.api.keyset.cmd
    361      if select('#', ...) == 1 and type(select(1, ...)) == 'table' then
    362        --- @type vim.api.keyset.cmd
    363        opts = select(1, ...)
    364 
    365        -- Move indexed positions in opts to opt.args
    366        if opts[1] and not opts.args then
    367          opts.args = {}
    368          for i = 1, VIM_CMD_ARG_MAX do
    369            if not opts[i] then
    370              break
    371            end
    372            opts.args[i] = opts[i]
    373            --- @diagnostic disable-next-line: no-unknown
    374            opts[i] = nil
    375          end
    376        end
    377      else
    378        opts = { args = { ... } }
    379      end
    380      opts.cmd = command
    381      return vim.api.nvim_cmd(opts, {})
    382    end
    383    return t[command]
    384  end,
    385 })
    386 
    387 --- @class (private) vim.var_accessor
    388 --- @field [string] any
    389 --- @field [integer] vim.var_accessor
    390 
    391 --- @class (private) vim.g: { [string]: any }
    392 --- @class (private) vim.b: vim.var_accessor
    393 --- @class (private) vim.w: vim.var_accessor
    394 --- @class (private) vim.t: vim.var_accessor
    395 
    396 -- These are the vim.env/v/g/o/bo/wo variable magic accessors.
    397 do
    398  --- @param scope string
    399  --- @param handle? false|integer
    400  --- @return vim.var_accessor
    401  local function make_dict_accessor(scope, handle)
    402    vim.validate('scope', scope, 'string')
    403    local mt = {}
    404    function mt:__newindex(k, v)
    405      return vim._setvar(scope, handle or 0, k, v)
    406    end
    407    function mt:__index(k)
    408      if handle == nil and type(k) == 'number' then
    409        return make_dict_accessor(scope, k)
    410      end
    411      return vim._getvar(scope, handle or 0, k)
    412    end
    413    return setmetatable({}, mt)
    414  end
    415 
    416  vim.g = make_dict_accessor('g', false) --[[@as vim.g]]
    417  vim.v = make_dict_accessor('v', false) --[[@as vim.v]]
    418  vim.b = make_dict_accessor('b') --[[@as vim.b]]
    419  vim.w = make_dict_accessor('w') --[[@as vim.w]]
    420  vim.t = make_dict_accessor('t') --[[@as vim.t]]
    421 end
    422 
    423 --- @deprecated
    424 --- Gets a dict of line segment ("chunk") positions for the region from `pos1` to `pos2`.
    425 ---
    426 --- Input and output positions are byte positions, (0,0)-indexed. "End of line" column
    427 --- position (for example, |linewise| visual selection) is returned as |v:maxcol| (big number).
    428 ---
    429 ---@param bufnr integer Buffer number, or 0 for current buffer
    430 ---@param pos1 integer[]|string Start of region as a (line, column) tuple or |getpos()|-compatible string
    431 ---@param pos2 integer[]|string End of region as a (line, column) tuple or |getpos()|-compatible string
    432 ---@param regtype string [setreg()]-style selection type
    433 ---@param inclusive boolean Controls whether the ending column is inclusive (see also 'selection').
    434 ---@return table region Dict of the form `{linenr = {startcol,endcol}}`. `endcol` is exclusive, and
    435 ---whole lines are returned as `{startcol,endcol} = {0,-1}`.
    436 function vim.region(bufnr, pos1, pos2, regtype, inclusive)
    437  vim.deprecate('vim.region', 'vim.fn.getregionpos()', '0.13')
    438 
    439  if not vim.api.nvim_buf_is_loaded(bufnr) then
    440    vim.fn.bufload(bufnr)
    441  end
    442 
    443  if type(pos1) == 'string' then
    444    local pos = vim.fn.getpos(pos1)
    445    pos1 = { pos[2] - 1, pos[3] - 1 }
    446  end
    447  if type(pos2) == 'string' then
    448    local pos = vim.fn.getpos(pos2)
    449    pos2 = { pos[2] - 1, pos[3] - 1 }
    450  end
    451 
    452  if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then
    453    pos1, pos2 = pos2, pos1 --- @type [integer, integer], [integer, integer]
    454  end
    455 
    456  -- getpos() may return {0,0,0,0}
    457  if pos1[1] < 0 or pos1[2] < 0 then
    458    return {}
    459  end
    460 
    461  -- check that region falls within current buffer
    462  local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
    463  pos1[1] = math.min(pos1[1], buf_line_count - 1)
    464  pos2[1] = math.min(pos2[1], buf_line_count - 1)
    465 
    466  -- in case of block selection, columns need to be adjusted for non-ASCII characters
    467  -- TODO: handle double-width characters
    468  if regtype:byte() == 22 then
    469    local bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
    470    pos1[2] = vim.str_utfindex(bufline, 'utf-32', pos1[2])
    471  end
    472 
    473  local region = {}
    474  for l = pos1[1], pos2[1] do
    475    local c1 --- @type number
    476    local c2 --- @type number
    477    if regtype:byte() == 22 then -- block selection: take width from regtype
    478      c1 = pos1[2]
    479      c2 = c1 + tonumber(regtype:sub(2))
    480      -- and adjust for non-ASCII characters
    481      local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
    482      local utflen = vim.str_utfindex(bufline, 'utf-32', #bufline)
    483      if c1 <= utflen then
    484        c1 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c1)))
    485      else
    486        c1 = #bufline + 1
    487      end
    488      if c2 <= utflen then
    489        c2 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c2)))
    490      else
    491        c2 = #bufline + 1
    492      end
    493    elseif regtype == 'V' then -- linewise selection, always return whole line
    494      c1 = 0
    495      c2 = -1
    496    else
    497      c1 = (l == pos1[1]) and pos1[2] or 0
    498      if inclusive and l == pos2[1] then
    499        local bufline = vim.api.nvim_buf_get_lines(bufnr, pos2[1], pos2[1] + 1, true)[1]
    500        pos2[2] = vim.fn.byteidx(bufline, vim.fn.charidx(bufline, pos2[2]) + 1)
    501      end
    502      c2 = (l == pos2[1]) and pos2[2] or -1
    503    end
    504    table.insert(region, l, { c1, c2 })
    505  end
    506  return region
    507 end
    508 
    509 --- Defers calling {fn} until {timeout} ms passes.
    510 ---
    511 --- Use to do a one-shot timer that calls {fn}
    512 --- Note: The {fn} is |vim.schedule()|d automatically, so API functions are
    513 --- safe to call.
    514 ---@param fn function Callback to call once `timeout` expires
    515 ---@param timeout integer Number of milliseconds to wait before calling `fn`
    516 ---@return uv.uv_timer_t timer luv timer object
    517 function vim.defer_fn(fn, timeout)
    518  vim.validate('fn', fn, 'callable', true)
    519 
    520  local timer = assert(vim.uv.new_timer())
    521  timer:start(timeout, 0, function()
    522    local _, err = vim.schedule(function()
    523      if not timer:is_closing() then
    524        timer:close()
    525      end
    526 
    527      fn()
    528    end)
    529 
    530    if err then
    531      timer:close()
    532    end
    533  end)
    534 
    535  return timer
    536 end
    537 
    538 --- Displays a notification to the user.
    539 ---
    540 --- This function can be overridden by plugins to display notifications using
    541 --- a custom provider (such as the system notification provider). By default,
    542 --- writes to |:messages|.
    543 ---@param msg string Content of the notification to show to the user.
    544 ---@param level integer|nil One of the values from |vim.log.levels|.
    545 ---@param opts table|nil Optional parameters. Unused by default.
    546 ---@diagnostic disable-next-line: unused-local
    547 function vim.notify(msg, level, opts) -- luacheck: no unused args
    548  local chunks = { { msg, level == vim.log.levels.WARN and 'WarningMsg' or nil } }
    549  vim.api.nvim_echo(chunks, true, { err = level == vim.log.levels.ERROR })
    550 end
    551 
    552 do
    553  local notified = {} --- @type table<string,true>
    554 
    555  --- Displays a notification only one time.
    556  ---
    557  --- Like |vim.notify()|, but subsequent calls with the same message will not
    558  --- display a notification.
    559  ---
    560  ---@param msg string Content of the notification to show to the user.
    561  ---@param level integer|nil One of the values from |vim.log.levels|.
    562  ---@param opts table|nil Optional parameters. Unused by default.
    563  ---@return boolean true if message was displayed, else false
    564  function vim.notify_once(msg, level, opts)
    565    if not notified[msg] then
    566      vim.notify(msg, level, opts)
    567      notified[msg] = true
    568      return true
    569    end
    570    return false
    571  end
    572 end
    573 
    574 local on_key_cbs = {} --- @type table<integer,[function, table]>
    575 
    576 --- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
    577 --- yes every, input key.
    578 ---
    579 --- The Nvim command-line option |-w| is related but does not support callbacks
    580 --- and cannot be toggled dynamically.
    581 ---
    582 ---@note {fn} will be removed on error.
    583 ---@note {fn} won't be invoked recursively, i.e. if {fn} itself consumes input,
    584 ---           it won't be invoked for those keys.
    585 ---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
    586 ---
    587 ---@param fn nil|fun(key: string, typed: string): string? Function invoked for every input key,
    588 ---          after mappings have been applied but before further processing. Arguments
    589 ---          {key} and {typed} are raw keycodes, where {key} is the key after mappings
    590 ---          are applied, and {typed} is the key(s) before mappings are applied.
    591 ---          {typed} may be empty if {key} is produced by non-typed key(s) or by the
    592 ---          same typed key(s) that produced a previous {key}.
    593 ---          If {fn} returns an empty string, {key} is discarded/ignored, and if {key}
    594 ---          is [<Cmd>] then the "[<Cmd>]…[<CR>]" sequence is discarded as a whole.
    595 ---          When {fn} is `nil`, the callback associated with namespace {ns_id} is removed.
    596 ---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a
    597 ---                      new |nvim_create_namespace()| id.
    598 ---@param opts table? Optional parameters
    599 ---
    600 ---@see |keytrans()|
    601 ---
    602 ---@return integer Namespace id associated with {fn}. Or count of all callbacks
    603 ---if on_key() is called without arguments.
    604 function vim.on_key(fn, ns_id, opts)
    605  if fn == nil and ns_id == nil then
    606    return vim.tbl_count(on_key_cbs)
    607  end
    608 
    609  vim.validate('fn', fn, 'callable', true)
    610  vim.validate('ns_id', ns_id, 'number', true)
    611  vim.validate('opts', opts, 'table', true)
    612  opts = opts or {}
    613 
    614  if ns_id == nil or ns_id == 0 then
    615    ns_id = vim.api.nvim_create_namespace('')
    616  end
    617 
    618  on_key_cbs[ns_id] = fn and { fn, opts }
    619  return ns_id
    620 end
    621 
    622 --- Executes the on_key callbacks.
    623 ---@private
    624 function vim._on_key(buf, typed_buf)
    625  local failed = {} ---@type [integer, string][]
    626  local discard = false
    627  for k, v in pairs(on_key_cbs) do
    628    local fn = v[1]
    629    --- @type boolean, any
    630    local ok, rv = xpcall(function()
    631      return fn(buf, typed_buf)
    632    end, debug.traceback)
    633    if ok and rv ~= nil then
    634      if type(rv) == 'string' and #rv == 0 then
    635        discard = true
    636        -- break   -- Without break deliver to all callbacks even when it eventually discards.
    637        -- "break" does not make sense unless callbacks are sorted by ???.
    638      else
    639        ok = false
    640        rv = 'return string must be empty'
    641      end
    642    end
    643    if not ok then
    644      vim.on_key(nil, k)
    645      table.insert(failed, { k, rv })
    646    end
    647  end
    648 
    649  if #failed > 0 then
    650    local errmsg = ''
    651    for _, v in ipairs(failed) do
    652      errmsg = errmsg .. string.format('\nWith ns_id %d: %s', v[1], v[2])
    653    end
    654    error(errmsg)
    655  end
    656  return discard
    657 end
    658 
    659 --- Convert UTF-32, UTF-16 or UTF-8 {index} to byte index.
    660 --- If {strict_indexing} is false
    661 --- then an out of range index will return byte length
    662 --- instead of throwing an error.
    663 ---
    664 --- Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|.
    665 --- An {index} in the middle of a UTF-16 sequence is rounded upwards to
    666 --- the end of that sequence.
    667 ---@param s string
    668 ---@param encoding "utf-8"|"utf-16"|"utf-32"
    669 ---@param index integer
    670 ---@param strict_indexing? boolean # default: true
    671 ---@return integer
    672 function vim.str_byteindex(s, encoding, index, strict_indexing)
    673  if type(encoding) == 'number' then
    674    -- Legacy support for old API
    675    -- Parameters: ~
    676    --   • {str}        (`string`)
    677    --   • {index}      (`integer`)
    678    --   • {use_utf16}  (`boolean?`)
    679    vim.deprecate(
    680      'vim.str_byteindex',
    681      'vim.str_byteindex(s, encoding, index, strict_indexing)',
    682      '1.0'
    683    )
    684    local old_index = encoding
    685    local use_utf16 = index or false
    686    return vim._str_byteindex(s, old_index, use_utf16) or error('index out of range')
    687  end
    688 
    689  -- Avoid vim.validate for performance.
    690  if type(s) ~= 'string' or type(index) ~= 'number' then
    691    vim.validate('s', s, 'string')
    692    vim.validate('index', index, 'number')
    693  end
    694 
    695  local len = #s
    696 
    697  if index == 0 or len == 0 then
    698    return 0
    699  end
    700 
    701  if not utfs[encoding] then
    702    vim.validate('encoding', encoding, function(v)
    703      return utfs[v], 'invalid encoding'
    704    end)
    705  end
    706 
    707  if strict_indexing ~= nil and type(strict_indexing) ~= 'boolean' then
    708    vim.validate('strict_indexing', strict_indexing, 'boolean', true)
    709  end
    710  if strict_indexing == nil then
    711    strict_indexing = true
    712  end
    713 
    714  if encoding == 'utf-8' then
    715    if index > len then
    716      return strict_indexing and error('index out of range') or len
    717    end
    718    return index
    719  end
    720  return vim._str_byteindex(s, index, encoding == 'utf-16')
    721    or strict_indexing and error('index out of range')
    722    or len
    723 end
    724 
    725 --- Convert byte index to UTF-32, UTF-16 or UTF-8 indices. If {index} is not
    726 --- supplied, the length of the string is used. All indices are zero-based.
    727 ---
    728 --- If {strict_indexing} is false then an out of range index will return string
    729 --- length instead of throwing an error.
    730 --- Invalid UTF-8 bytes, and embedded surrogates are counted as one code point
    731 --- each. An {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
    732 --- that sequence.
    733 ---@param s string
    734 ---@param encoding "utf-8"|"utf-16"|"utf-32"
    735 ---@param index? integer
    736 ---@param strict_indexing? boolean # default: true
    737 ---@return integer
    738 function vim.str_utfindex(s, encoding, index, strict_indexing)
    739  if encoding == nil or type(encoding) == 'number' then
    740    -- Legacy support for old API
    741    -- Parameters: ~
    742    --   • {str}    (`string`)
    743    --   • {index}  (`integer?`)
    744    vim.deprecate(
    745      'vim.str_utfindex',
    746      'vim.str_utfindex(s, encoding, index, strict_indexing)',
    747      '1.0'
    748    )
    749    local old_index = encoding
    750    local col32, col16 = vim._str_utfindex(s, old_index) --[[@as integer,integer]]
    751    if not col32 or not col16 then
    752      error('index out of range')
    753    end
    754    -- Return (multiple): ~
    755    --     (`integer`) UTF-32 index
    756    --     (`integer`) UTF-16 index
    757    --- @diagnostic disable-next-line: redundant-return-value
    758    return col32, col16
    759  end
    760 
    761  if type(s) ~= 'string' or (index ~= nil and type(index) ~= 'number') then
    762    vim.validate('s', s, 'string')
    763    vim.validate('index', index, 'number', true)
    764  end
    765 
    766  if not index then
    767    index = math.huge
    768    strict_indexing = false
    769  end
    770 
    771  if index == 0 then
    772    return 0
    773  end
    774 
    775  if not utfs[encoding] then
    776    vim.validate('encoding', encoding, function(v)
    777      return utfs[v], 'invalid encoding'
    778    end)
    779  end
    780 
    781  if strict_indexing ~= nil and type(strict_indexing) ~= 'boolean' then
    782    vim.validate('strict_indexing', strict_indexing, 'boolean', true)
    783  end
    784  if strict_indexing == nil then
    785    strict_indexing = true
    786  end
    787 
    788  if encoding == 'utf-8' then
    789    local len = #s
    790    return index <= len and index or (strict_indexing and error('index out of range') or len)
    791  end
    792  local col32, col16 = vim._str_utfindex(s, index) --[[@as integer?,integer?]]
    793  local col = encoding == 'utf-16' and col16 or col32
    794  if col then
    795    return col
    796  end
    797  if strict_indexing then
    798    error('index out of range')
    799  end
    800  local max32, max16 = vim._str_utfindex(s)--[[@as integer integer]]
    801  return encoding == 'utf-16' and max16 or max32
    802 end
    803 
    804 --- Generates a list of possible completions for the str
    805 --- String has the pattern.
    806 ---
    807 --- 1. Can we get it to just return things in the global namespace with that name prefix
    808 --- 2. Can we get it to return things from global namespace even with `print(` in front.
    809 ---
    810 --- @param pat string
    811 --- @return any[], integer
    812 function vim._expand_pat(pat, env)
    813  env = env or _G
    814 
    815  if pat == '' then
    816    local result = vim.tbl_keys(env)
    817    table.sort(result)
    818    return result, 0
    819  end
    820 
    821  -- TODO: We can handle spaces in [] ONLY.
    822  --    We should probably do that at some point, just for cooler completion.
    823  -- TODO: We can suggest the variable names to go in []
    824  --    This would be difficult as well.
    825  --    Probably just need to do a smarter match than just `:match`
    826 
    827  -- Get the last part of the pattern
    828  local last_part = pat:match('[%w.:_%[%]\'"]+$')
    829  if not last_part then
    830    return {}, 0
    831  end
    832 
    833  local parts, search_index = vim._expand_pat_get_parts(last_part)
    834 
    835  local match_part = string.sub(last_part, search_index, #last_part)
    836  local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or ''
    837  local last_char = string.sub(last_part, #last_part)
    838 
    839  local final_env = env
    840 
    841  --- Allows submodules to be defined on a `vim.<module>` table without eager-loading the module.
    842  ---
    843  --- Cmdline completion (`:lua vim.lsp.c<tab>`) accesses `vim.lsp._submodules` when no other candidates.
    844  --- Cmdline completion (`:lua vim.lsp.completion.g<tab>`) will eager-load the module anyway. #33007
    845  ---
    846  --- @param m table
    847  --- @param k string
    848  --- @return any
    849  local function safe_tbl_get(m, k)
    850    local val = rawget(m, k)
    851    if val ~= nil then
    852      return val
    853    end
    854 
    855    local mt = getmetatable(m)
    856    if not mt then
    857      return m == vim and vim._extra[k] or nil
    858    end
    859 
    860    -- use mt.__index, _submodules as fallback
    861    if type(mt.__index) == 'table' then
    862      return rawget(mt.__index, k)
    863    end
    864 
    865    local sub = rawget(m, '_submodules')
    866    if sub and type(sub) == 'table' and rawget(sub, k) then
    867      -- Access the module to force _defer_require() to load the module.
    868      return m[k]
    869    end
    870  end
    871 
    872  for _, part in ipairs(parts) do
    873    if type(final_env) ~= 'table' then
    874      return {}, 0
    875    end
    876    local key --- @type any
    877 
    878    -- Normally, we just have a string
    879    -- Just attempt to get the string directly from the environment
    880    if type(part) == 'string' then
    881      key = part
    882    else
    883      -- However, sometimes you want to use a variable, and complete on it
    884      --    With this, you have the power.
    885 
    886      -- MY_VAR = "api"
    887      -- vim[MY_VAR]
    888      -- -> _G[MY_VAR] -> "api"
    889      local result_key = part[1]
    890      if not result_key then
    891        return {}, 0
    892      end
    893 
    894      local result = rawget(env, result_key)
    895 
    896      if result == nil then
    897        return {}, 0
    898      end
    899 
    900      key = result
    901    end
    902    final_env = safe_tbl_get(final_env, key)
    903 
    904    if not final_env then
    905      return {}, 0
    906    end
    907  end
    908 
    909  local keys = {} --- @type table<string,true>
    910 
    911  --- @param obj table<any,any>
    912  local function insert_keys(obj)
    913    for k, _ in pairs(obj) do
    914      if
    915        type(k) == 'string'
    916        and string.sub(k, 1, string.len(match_part)) == match_part
    917        and k:match('^[_%w]+$') ~= nil -- filter out invalid identifiers for field, e.g. 'foo#bar'
    918        and (last_char ~= '.' or string.sub(k, 1, 1) ~= '_') -- don't include private fields after '.'
    919      then
    920        keys[k] = true
    921      end
    922    end
    923  end
    924  ---@param acc table<string,any>
    925  local function _fold_to_map(acc, k, v)
    926    acc[k] = (v or true)
    927    return acc
    928  end
    929 
    930  if type(final_env) == 'table' then
    931    insert_keys(final_env)
    932    local sub = rawget(final_env, '_submodules')
    933    if type(sub) == 'table' then
    934      insert_keys(sub)
    935    end
    936    if final_env == vim then
    937      insert_keys(vim._extra)
    938    end
    939  end
    940 
    941  local mt = getmetatable(final_env)
    942  if mt and type(mt.__index) == 'table' then
    943    insert_keys(mt.__index)
    944  end
    945 
    946  -- Completion for dict accessors (special vim variables and vim.fn)
    947  if mt and vim.tbl_contains({ vim.g, vim.t, vim.w, vim.b, vim.v, vim.env, vim.fn }, final_env) then
    948    local prefix, type = unpack(
    949      vim.fn == final_env and { '', 'function' }
    950        or vim.g == final_env and { 'g:', 'var' }
    951        or vim.t == final_env and { 't:', 'var' }
    952        or vim.w == final_env and { 'w:', 'var' }
    953        or vim.b == final_env and { 'b:', 'var' }
    954        or vim.v == final_env and { 'v:', 'var' }
    955        or vim.env == final_env and { '', 'environment' }
    956        or { nil, nil }
    957    )
    958    assert(prefix and type, "Can't resolve final_env")
    959    local vars = vim.fn.getcompletion(prefix .. match_part, type) --- @type string[]
    960    insert_keys(vim
    961      .iter(vars)
    962      :map(function(s) ---@param s string
    963        s = s:gsub('[()]+$', '') -- strip '(' and ')' for function completions
    964        return s:sub(#prefix + 1) -- strip the prefix, e.g., 'g:foo' => 'foo'
    965      end)
    966      :fold({}, _fold_to_map))
    967  end
    968 
    969  -- Completion for option accessors (full names only)
    970  if
    971    mt
    972    and vim.tbl_contains(
    973      { vim.o, vim.go, vim.bo, vim.wo, vim.opt, vim.opt_local, vim.opt_global },
    974      final_env
    975    )
    976  then
    977    --- @type fun(option_name: string, option: vim.api.keyset.get_option_info): boolean
    978    local filter = function(_, _)
    979      return true
    980    end
    981    if vim.bo == final_env then
    982      filter = function(_, option)
    983        return option.scope == 'buf'
    984      end
    985    elseif vim.wo == final_env then
    986      filter = function(_, option)
    987        return option.scope == 'win'
    988      end
    989    end
    990 
    991    --- @type table<string, vim.api.keyset.get_option_info>
    992    local options = vim.api.nvim_get_all_options_info()
    993    insert_keys(vim.iter(options):filter(filter):fold({}, _fold_to_map))
    994  end
    995 
    996  keys = vim.tbl_keys(keys)
    997  table.sort(keys)
    998 
    999  return keys, #prefix_match_pat
   1000 end
   1001 
   1002 --- @param lua_string string
   1003 --- @return (string|string[])[], integer
   1004 vim._expand_pat_get_parts = function(lua_string)
   1005  local parts = {}
   1006 
   1007  local accumulator, search_index = '', 1
   1008  local in_brackets = false
   1009  local bracket_end = -1 --- @type integer?
   1010  local string_char = nil
   1011  for idx = 1, #lua_string do
   1012    local s = lua_string:sub(idx, idx)
   1013 
   1014    if not in_brackets and (s == '.' or s == ':') then
   1015      table.insert(parts, accumulator)
   1016      accumulator = ''
   1017 
   1018      search_index = idx + 1
   1019    elseif s == '[' then
   1020      in_brackets = true
   1021 
   1022      table.insert(parts, accumulator)
   1023      accumulator = ''
   1024 
   1025      search_index = idx + 1
   1026    elseif in_brackets then
   1027      if idx == bracket_end then
   1028        in_brackets = false
   1029        search_index = idx + 1
   1030 
   1031        if string_char == 'VAR' then
   1032          table.insert(parts, { accumulator })
   1033          accumulator = ''
   1034 
   1035          string_char = nil
   1036        end
   1037      elseif not string_char then
   1038        bracket_end = string.find(lua_string, ']', idx, true)
   1039 
   1040        if s == '"' or s == "'" then
   1041          string_char = s
   1042        elseif s ~= ' ' then
   1043          string_char = 'VAR'
   1044          accumulator = s
   1045        end
   1046      elseif string_char then
   1047        if string_char ~= s then
   1048          accumulator = accumulator .. s
   1049        else
   1050          table.insert(parts, accumulator)
   1051          accumulator = ''
   1052 
   1053          string_char = nil
   1054        end
   1055      end
   1056    else
   1057      accumulator = accumulator .. s
   1058    end
   1059  end
   1060 
   1061  --- @param val any[]
   1062  parts = vim.tbl_filter(function(val)
   1063    return #val > 0
   1064  end, parts)
   1065 
   1066  return parts, search_index
   1067 end
   1068 
   1069 do
   1070  -- Ideally we should just call complete() inside omnifunc, though there are
   1071  -- some bugs, so fake the two-step dance for now.
   1072  local matches --- @type any[]
   1073 
   1074  --- Omnifunc for completing Lua values from the runtime Lua interpreter,
   1075  --- similar to the builtin completion for the `:lua` command.
   1076  ---
   1077  --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a Lua buffer.
   1078  --- @param find_start 1|0
   1079  function vim.lua_omnifunc(find_start, _)
   1080    if find_start == 1 then
   1081      local line = vim.api.nvim_get_current_line()
   1082      local prefix = string.sub(line, 1, vim.api.nvim_win_get_cursor(0)[2])
   1083      local pos
   1084      matches, pos = vim._expand_pat(prefix)
   1085      return (#matches > 0 and pos) or -1
   1086    else
   1087      return matches
   1088    end
   1089  end
   1090 end
   1091 
   1092 --- @param inspect_strings boolean use vim.inspect() for strings
   1093 function vim._print(inspect_strings, ...)
   1094  local msg = {}
   1095  for i = 1, select('#', ...) do
   1096    local o = select(i, ...)
   1097    if not inspect_strings and type(o) == 'string' then
   1098      table.insert(msg, o)
   1099    else
   1100      table.insert(msg, vim.inspect(o, { newline = '\n', indent = '  ' }))
   1101    end
   1102  end
   1103  print(table.concat(msg, '\n'))
   1104  return ...
   1105 end
   1106 
   1107 --- "Pretty prints" the given arguments and returns them unmodified.
   1108 ---
   1109 --- Example:
   1110 ---
   1111 --- ```lua
   1112 --- local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))
   1113 --- ```
   1114 ---
   1115 --- @see |vim.inspect()|
   1116 --- @see |:=|
   1117 --- @param ... any
   1118 --- @return any # given arguments.
   1119 function vim.print(...)
   1120  return vim._print(false, ...)
   1121 end
   1122 
   1123 --- Translates keycodes.
   1124 ---
   1125 --- Example:
   1126 ---
   1127 --- ```lua
   1128 --- local k = vim.keycode
   1129 --- vim.g.mapleader = k'<bs>'
   1130 --- ```
   1131 ---
   1132 --- @param str string String to be converted.
   1133 --- @return string
   1134 --- @see |nvim_replace_termcodes()|
   1135 function vim.keycode(str)
   1136  return vim.api.nvim_replace_termcodes(str, true, true, true)
   1137 end
   1138 
   1139 --- @param server_addr string
   1140 --- @param connect_error string
   1141 function vim._cs_remote(rcid, server_addr, connect_error, args)
   1142  --- @return string
   1143  local function connection_failure_errmsg(consequence)
   1144    local explanation --- @type string
   1145    if server_addr == '' then
   1146      explanation = 'No server specified with --server'
   1147    else
   1148      explanation = "Failed to connect to '" .. server_addr .. "'"
   1149      if connect_error ~= '' then
   1150        explanation = explanation .. ': ' .. connect_error
   1151      end
   1152    end
   1153    return 'E247: ' .. explanation .. '. ' .. consequence
   1154  end
   1155 
   1156  local f_silent = false
   1157  local f_tab = false
   1158 
   1159  local subcmd = string.sub(args[1], 10)
   1160  if subcmd == 'tab' then
   1161    f_tab = true
   1162  elseif subcmd == 'silent' then
   1163    f_silent = true
   1164  elseif
   1165    subcmd == 'wait'
   1166    or subcmd == 'wait-silent'
   1167    or subcmd == 'tab-wait'
   1168    or subcmd == 'tab-wait-silent'
   1169  then
   1170    return { errmsg = 'E5600: Wait commands not yet implemented in Nvim' }
   1171  elseif subcmd == 'tab-silent' then
   1172    f_tab = true
   1173    f_silent = true
   1174  elseif subcmd == 'send' then
   1175    if rcid == 0 then
   1176      return { errmsg = connection_failure_errmsg('Send failed.') }
   1177    end
   1178    vim.rpcrequest(rcid, 'nvim_input', args[2])
   1179    return { should_exit = true, tabbed = false }
   1180  elseif subcmd == 'expr' then
   1181    if rcid == 0 then
   1182      return { errmsg = connection_failure_errmsg('Send expression failed.') }
   1183    end
   1184    local res = tostring(vim.rpcrequest(rcid, 'nvim_eval', args[2]))
   1185    return { result = res, should_exit = true, tabbed = false }
   1186  elseif subcmd ~= '' then
   1187    return { errmsg = 'Unknown option argument: ' .. tostring(args[1]) }
   1188  end
   1189 
   1190  if rcid == 0 then
   1191    if not f_silent then
   1192      vim.notify(connection_failure_errmsg('Editing locally'), vim.log.levels.WARN)
   1193    end
   1194  else
   1195    local command = {}
   1196    if f_tab then
   1197      table.insert(command, 'tab')
   1198    end
   1199    table.insert(command, 'drop')
   1200    for i = 2, #args do
   1201      table.insert(command, vim.fn.fnameescape(args[i]))
   1202    end
   1203    vim.fn.rpcrequest(rcid, 'nvim_command', table.concat(command, ' '))
   1204  end
   1205 
   1206  return {
   1207    should_exit = rcid ~= 0,
   1208    tabbed = f_tab,
   1209  }
   1210 end
   1211 
   1212 do
   1213  local function truncated_echo(msg)
   1214    -- Truncate message to avoid hit-enter-prompt
   1215    local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace
   1216    local msg_truncated = string.sub(msg, 1, max_width)
   1217    vim.api.nvim_echo({ { msg_truncated, 'WarningMsg' } }, true, {})
   1218  end
   1219 
   1220  local notified = false
   1221 
   1222  function vim._truncated_echo_once(msg)
   1223    if not notified then
   1224      truncated_echo(msg)
   1225      notified = true
   1226      return true
   1227    end
   1228    return false
   1229  end
   1230 end
   1231 
   1232 --- This is basically the same as debug.traceback(), except the full paths are shown.
   1233 local function traceback()
   1234  local level = 4
   1235  local backtrace = { 'stack traceback:' }
   1236  while true do
   1237    local info = debug.getinfo(level, 'Sl')
   1238    if not info then
   1239      break
   1240    end
   1241    local msg = ('  %s:%s'):format(info.source:gsub('^@', ''), info.currentline)
   1242    table.insert(backtrace, msg)
   1243    level = level + 1
   1244  end
   1245  return table.concat(backtrace, '\n')
   1246 end
   1247 
   1248 --- Shows a deprecation message to the user.
   1249 ---
   1250 ---@param name        string     Deprecated feature (function, API, etc.).
   1251 ---@param alternative string|nil Suggested alternative feature.
   1252 ---@param version     string     Version when the deprecated function will be removed.
   1253 ---@param plugin      string|nil Name of the plugin that owns the deprecated feature.
   1254 ---                              Defaults to "Nvim".
   1255 ---@param backtrace   boolean|nil Prints backtrace. Defaults to true.
   1256 ---
   1257 ---@return string|nil # Deprecated message, or nil if no message was shown.
   1258 function vim.deprecate(name, alternative, version, plugin, backtrace)
   1259  plugin = plugin or 'Nvim'
   1260  if plugin == 'Nvim' then
   1261    require('vim.deprecated.health').add(name, version, traceback(), alternative)
   1262 
   1263    -- Show a warning only if feature is hard-deprecated (see MAINTAIN.md).
   1264    -- Example: if removal `version` is 0.12 (soft-deprecated since 0.10-dev), show warnings
   1265    -- starting at 0.11, including 0.11-dev.
   1266    local major, minor = version:match('(%d+)%.(%d+)')
   1267    major, minor = tonumber(major), tonumber(minor)
   1268    local nvim_major = 0 --- Current Nvim major version.
   1269 
   1270    -- We can't "subtract" from a major version, so:
   1271    --  * Always treat `major > nvim_major` as soft-deprecation.
   1272    --  * Compare `minor - 1` if `major == nvim_major`.
   1273    if major > nvim_major then
   1274      return -- Always soft-deprecation (see MAINTAIN.md).
   1275    end
   1276 
   1277    local hard_deprecated_since = string.format('nvim-%d.%d', major, minor - 1)
   1278    if major == nvim_major and vim.fn.has(hard_deprecated_since) == 0 then
   1279      return
   1280    end
   1281 
   1282    local msg = ('%s is deprecated. Run ":checkhealth vim.deprecated" for more information'):format(
   1283      name
   1284    )
   1285 
   1286    local displayed = vim._truncated_echo_once(msg)
   1287    return displayed and msg or nil
   1288  else
   1289    vim.validate('name', name, 'string')
   1290    vim.validate('alternative', alternative, 'string', true)
   1291    vim.validate('version', version, 'string', true)
   1292    vim.validate('plugin', plugin, 'string', true)
   1293 
   1294    local msg = ('%s is deprecated'):format(name)
   1295    msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.')
   1296    msg = ('%s\nFeature will be removed in %s %s'):format(msg, plugin, version)
   1297    local displayed = vim.notify_once(msg, vim.log.levels.WARN)
   1298    if displayed and backtrace ~= false then
   1299      vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN)
   1300    end
   1301    return displayed and msg or nil
   1302  end
   1303 end
   1304 
   1305 require('vim._core.options')
   1306 
   1307 --- Remove at Nvim 1.0
   1308 ---@deprecated
   1309 vim.loop = vim.uv
   1310 
   1311 -- Deprecated. Remove at Nvim 2.0
   1312 vim.highlight = vim._defer_deprecated_module('vim.highlight', 'vim.hl')
   1313 
   1314 return vim