diff options
Diffstat (limited to 'src/nvim/lua/vim.lua')
-rw-r--r-- | src/nvim/lua/vim.lua | 341 |
1 files changed, 289 insertions, 52 deletions
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index a03e97490d..820b237c4f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -33,35 +33,35 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util +local vim = vim +assert(vim) -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} -local function _system(cmd) - local out = vim.api.nvim_call_function('system', { cmd }) - local err = vim.api.nvim_get_vvar('shell_error') +function vim._system(cmd) + local out = vim.fn.system(cmd) + local err = vim.v.shell_error return err, out end -- Gets process info from the `ps` command. -- Used by nvim_get_proc() as a fallback. -local function _os_proc_info(pid) +function vim._os_proc_info(pid) if pid == nil or pid <= 0 or type(pid) ~= 'number' then error('invalid pid') end local cmd = { 'ps', '-p', pid, '-o', 'comm=', } - local err, name = _system(cmd) - if 1 == err and string.gsub(name, '%s*', '') == '' then + local err, name = vim._system(cmd) + if 1 == err and vim.trim(name) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end - local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) -- Remove trailing whitespace. - name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '') - ppid = string.gsub(ppid, '%s+$', '') - ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + name = vim.trim(name):gsub('^.*/', '') + ppid = tonumber(ppid) or -1 return { name = name, pid = pid, @@ -71,20 +71,19 @@ end -- Gets process children from the `pgrep` command. -- Used by nvim_get_proc_children() as a fallback. -local function _os_proc_children(ppid) +function vim._os_proc_children(ppid) if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then error('invalid ppid') end local cmd = { 'pgrep', '-P', ppid, } - local err, rv = _system(cmd) - if 1 == err and string.gsub(rv, '%s*', '') == '' then + local err, rv = vim._system(cmd) + if 1 == err and vim.trim(rv) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end local children = {} - for s in string.gmatch(rv, '%S+') do + for s in rv:gmatch('%S+') do local i = tonumber(s) if i ~= nil then table.insert(children, i) @@ -98,7 +97,7 @@ end -- Last inserted paths. Used to clear out items from package.[c]path when they -- are no longer in &runtimepath. local last_nvim_paths = {} -local function _update_package_paths() +function vim._update_package_paths() local cur_nvim_paths = {} local rtps = vim.api.nvim_list_runtime_paths() local sep = package.config:sub(1, 1) @@ -162,22 +161,35 @@ local function inspect(object, options) -- luacheck: no unused error(object, options) -- Stub for gen_vimdoc.py end ---- Paste handler, invoked by |nvim_paste()| when a conforming UI ---- (such as the |TUI|) pastes text into the editor. ---- ---@see |paste| ---- ---@param lines |readfile()|-style list of lines to paste. |channel-lines| ---@param phase -1: "non-streaming" paste: the call contains all lines. ---- If paste is "streamed", `phase` indicates the stream state: ---- - 1: starts the paste (exactly once) ---- - 2: continues the paste (zero or more times) ---- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. -local function paste(lines, phase) end -- luacheck: no unused -paste = (function() +do local tdots, tick, got_line1 = 0, 0, false - return function(lines, phase) + + --- Paste handler, invoked by |nvim_paste()| when a conforming UI + --- (such as the |TUI|) pastes text into the editor. + --- + --- Example: To remove ANSI color codes when pasting: + --- <pre> + --- vim.paste = (function(overridden) + --- return function(lines, phase) + --- for i,line in ipairs(lines) do + --- -- Scrub ANSI color codes from paste input. + --- lines[i] = line:gsub('\27%[[0-9;mK]+', '') + --- end + --- overridden(lines, phase) + --- end + --- end)(vim.paste) + --- </pre> + --- + --@see |paste| + --- + --@param lines |readfile()|-style list of lines to paste. |channel-lines| + --@param phase -1: "non-streaming" paste: the call contains all lines. + --- If paste is "streamed", `phase` indicates the stream state: + --- - 1: starts the paste (exactly once) + --- - 2: continues the paste (zero or more times) + --- - 3: ends the paste (exactly once) + --@returns false if client should cancel the paste. + function vim.paste(lines, phase) local call = vim.api.nvim_call_function local now = vim.loop.now() local mode = call('mode', {}):sub(1,1) @@ -189,14 +201,29 @@ paste = (function() if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. got_line1 = (#lines > 1) vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') -- Scrub. + local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. - if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then + elseif mode ~= 'c' then + if phase < 2 and mode:find('^[vV\22sS\19]') then + vim.api.nvim_command([[exe "normal! \<Del>"]]) + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and not mode:find('^[iRt]') then vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') + elseif phase < 2 and mode == 'R' then + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) else vim.api.nvim_put(lines, 'c', false, true) end @@ -214,42 +241,252 @@ paste = (function() end return true -- Paste will not continue if not returning `true`. end -end)() +end --- Defers callback `cb` until the Nvim API is safe to call. --- ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| -local function schedule_wrap(cb) +function vim.schedule_wrap(cb) return (function (...) local args = {...} vim.schedule(function() cb(unpack(args)) end) end) end +--- <Docs described in |vim.empty_dict()| > +--@private +function vim.empty_dict() + return setmetatable({}, vim._empty_dict_mt) +end + +-- vim.fn.{func}(...) +vim.fn = setmetatable({}, { + __index = function(t, key) + local function _fn(...) + return vim.call(key, ...) + end + t[key] = _fn + return _fn + end +}) + +vim.funcref = function(viml_func_name) + return vim.fn[viml_func_name] +end + +-- These are for loading runtime modules lazily since they aren't available in +-- the nvim binary as specified in executor.c local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') return t.inspect - elseif require('vim.shared')[key] ~= nil then - -- Expose all `vim.shared` functions on the `vim` module. - t[key] = require('vim.shared')[key] + elseif key == 'treesitter' then + t.treesitter = require('vim.treesitter') + return t.treesitter + elseif require('vim.uri')[key] ~= nil then + -- Expose all `vim.uri` functions on the `vim` module. + t[key] = require('vim.uri')[key] return t[key] + elseif key == 'lsp' then + t.lsp = require('vim.lsp') + return t.lsp + elseif key == 'highlight' then + t.highlight = require('vim.highlight') + return t.highlight end end -local module = { - _update_package_paths = _update_package_paths, - _os_proc_children = _os_proc_children, - _os_proc_info = _os_proc_info, - _system = _system, - paste = paste, - schedule_wrap = schedule_wrap, -} - -setmetatable(module, { +setmetatable(vim, { __index = __index }) +-- An easier alias for commands. +vim.cmd = vim.api.nvim_command + +-- These are the vim.env/v/g/o/bo/wo variable magic accessors. +do + local a = vim.api + local validate = vim.validate + local function make_meta_accessor(get, set, del) + validate { + get = {get, 'f'}; + set = {set, 'f'}; + del = {del, 'f', true}; + } + local mt = {} + if del then + function mt:__newindex(k, v) + if v == nil then + return del(k) + end + return set(k, v) + end + else + function mt:__newindex(k, v) + return set(k, v) + end + end + function mt:__index(k) + return get(k) + end + return setmetatable({}, mt) + end + local function pcall_ret(status, ...) + if status then return ... end + end + local function nil_wrap(fn) + return function(...) + return pcall_ret(pcall(fn, ...)) + end + end + + vim.b = make_meta_accessor( + nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end), + function(v, k) return a.nvim_buf_set_var(0, v, k) end, + function(v) return a.nvim_buf_del_var(0, v) end + ) + vim.w = make_meta_accessor( + nil_wrap(function(v) return a.nvim_win_get_var(0, v) end), + function(v, k) return a.nvim_win_set_var(0, v, k) end, + function(v) return a.nvim_win_del_var(0, v) end + ) + vim.t = make_meta_accessor( + nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end), + function(v, k) return a.nvim_tabpage_set_var(0, v, k) end, + function(v) return a.nvim_tabpage_del_var(0, v) end + ) + vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) + vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) + + local function getenv(k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil + end + return v + end + vim.env = make_meta_accessor(getenv, vim.fn.setenv) + -- TODO(ashkan) if/when these are available from an API, generate them + -- instead of hardcoding. + local window_options = { + arab = true; arabic = true; breakindent = true; breakindentopt = true; + bri = true; briopt = true; cc = true; cocu = true; + cole = true; colorcolumn = true; concealcursor = true; conceallevel = true; + crb = true; cuc = true; cul = true; cursorbind = true; + cursorcolumn = true; cursorline = true; diff = true; fcs = true; + fdc = true; fde = true; fdi = true; fdl = true; + fdm = true; fdn = true; fdt = true; fen = true; + fillchars = true; fml = true; fmr = true; foldcolumn = true; + foldenable = true; foldexpr = true; foldignore = true; foldlevel = true; + foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true; + foldtext = true; lbr = true; lcs = true; linebreak = true; + list = true; listchars = true; nu = true; number = true; + numberwidth = true; nuw = true; previewwindow = true; pvw = true; + relativenumber = true; rightleft = true; rightleftcmd = true; rl = true; + rlc = true; rnu = true; scb = true; scl = true; + scr = true; scroll = true; scrollbind = true; signcolumn = true; + spell = true; statusline = true; stl = true; wfh = true; + wfw = true; winbl = true; winblend = true; winfixheight = true; + winfixwidth = true; winhighlight = true; winhl = true; wrap = true; + } + local function new_buf_opt_accessor(bufnr) + local function get(k) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + if bufnr == nil and type(k) == "number" then + return new_buf_opt_accessor(k) + end + return a.nvim_buf_get_option(bufnr or 0, k) + end + local function set(k, v) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_set_option(bufnr or 0, k, v) + end + return make_meta_accessor(get, set) + end + vim.bo = new_buf_opt_accessor(nil) + local function new_win_opt_accessor(winnr) + local function get(k) + if winnr == nil and type(k) == "number" then + return new_win_opt_accessor(k) + end + return a.nvim_win_get_option(winnr or 0, k) + end + local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end + return make_meta_accessor(get, set) + end + vim.wo = new_win_opt_accessor(nil) +end + +--- Get a table of lines with start, end columns for a region marked by two points +--- +--@param bufnr number of buffer +--@param pos1 (line, column) tuple marking beginning of region +--@param pos2 (line, column) tuple marking end of region +--@param regtype type of selection (:help setreg) +--@param inclusive boolean indicating whether the selection is end-inclusive +--@return region lua table of the form {linenr = {startcol,endcol}} +function vim.region(bufnr, pos1, pos2, regtype, inclusive) + if not vim.api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end + + -- in case of block selection, columns need to be adjusted for non-ASCII characters + -- TODO: handle double-width characters + local bufline + if regtype:byte() == 22 then + bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1] + pos1[2] = vim.str_utfindex(bufline, pos1[2]) + end + + local region = {} + for l = pos1[1], pos2[1] do + local c1, c2 + if regtype:byte() == 22 then -- block selection: take width from regtype + c1 = pos1[2] + c2 = c1 + regtype:sub(2) + -- and adjust for non-ASCII characters + bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1] + if c1 < #bufline then + c1 = vim.str_byteindex(bufline, c1) + end + if c2 < #bufline then + c2 = vim.str_byteindex(bufline, c2) + end + else + c1 = (l == pos1[1]) and (pos1[2]) or 0 + c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1 + end + table.insert(region, l, {c1, c2}) + end + return region +end + +--- Defers calling `fn` until `timeout` ms passes. +--- +--- Use to do a one-shot timer that calls `fn` +--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are +--- safe to call. +--@param fn Callback to call once `timeout` expires +--@param timeout Number of milliseconds to wait before calling `fn` +--@return timer luv timer object +function vim.defer_fn(fn, timeout) + vim.validate { fn = { fn, 'c', true}; } + local timer = vim.loop.new_timer() + timer:start(timeout, 0, vim.schedule_wrap(function() + timer:stop() + timer:close() + + fn() + end)) + + return timer +end + return module |