diff options
Diffstat (limited to 'runtime/lua')
69 files changed, 4003 insertions, 2603 deletions
diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua index ca0e913d51..23c078cb87 100644 --- a/runtime/lua/_vim9script.lua +++ b/runtime/lua/_vim9script.lua @@ -82,7 +82,7 @@ local vim9 = (function() end M.index = function(obj, idx) - if vim.tbl_islist(obj) then + if vim.islist(obj) then if idx < 0 then return obj[#obj + idx + 1] else @@ -127,7 +127,7 @@ local vim9 = (function() assert(type(finish) == 'number') local slicer - if vim.tbl_islist(obj) then + if vim.islist(obj) then slicer = vim.list_slice elseif type(obj) == 'string' then slicer = string.sub @@ -168,7 +168,7 @@ local vim9 = (function() end M.iter = function(expr) - if vim.tbl_islist(expr) then + if vim.islist(expr) then return ipairs(expr) else return pairs(expr) @@ -234,7 +234,7 @@ vim9['convert'] = (function() elseif type(val) == 'table' then if vim.tbl_isempty(val) then return vim.empty_dict() - elseif vim.tbl_islist(val) then + elseif vim.islist(val) then error(string.format('Cannot pass list to dictionary? %s', vim.inspect(val))) else return val @@ -280,7 +280,7 @@ vim9['fn'] = (function() error("haven't written this code yet") end - if vim.tbl_islist(right) then + if vim.islist(right) then vim.list_extend(left, right) return left else diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 49d63807a6..dcd7425c29 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -1,31 +1,79 @@ -local M = {} +--- @brief +--- Nvim supports EditorConfig. When a file is opened, after running |ftplugin|s +--- and |FileType| autocommands, Nvim searches all parent directories of that file +--- for ".editorconfig" files, parses them, and applies any properties that match +--- the opened file. Think of it like 'modeline' for an entire (recursive) +--- directory. For more information see https://editorconfig.org/. + +--- @brief [g:editorconfig]() [b:editorconfig]() +--- +--- EditorConfig is enabled by default. To disable it, add to your config: +--- ```lua +--- vim.g.editorconfig = false +--- ``` +--- +--- (Vimscript: `let g:editorconfig = v:false`). It can also be disabled +--- per-buffer by setting the [b:editorconfig] buffer-local variable to `false`. +--- +--- Nvim stores the applied properties in [b:editorconfig] if it is not `false`. + +--- @brief [editorconfig-custom-properties]() +--- +--- New properties can be added by adding a new entry to the "properties" table. +--- The table key is a property name and the value is a callback function which +--- accepts the number of the buffer to be modified, the value of the property +--- in the `.editorconfig` file, and (optionally) a table containing all of the +--- other properties and their values (useful for properties which depend on other +--- properties). The value is always a string and must be coerced if necessary. +--- Example: +--- +--- ```lua +--- +--- require('editorconfig').properties.foo = function(bufnr, val, opts) +--- if opts.charset and opts.charset ~= "utf-8" then +--- error("foo can only be set when charset is utf-8", 0) +--- end +--- vim.b[bufnr].foo = val +--- end +--- +--- ``` + +--- @brief [editorconfig-properties]() +--- +--- The following properties are supported by default: --- @type table<string,fun(bufnr: integer, val: string, opts?: table)> -M.properties = {} +local properties = {} +--- @private --- Modified version of the builtin assert that does not include error position information --- ----@param v any Condition ----@param message string Error message to display if condition is false or nil ----@return any v if not false or nil, otherwise an error is displayed ---- ----@private +--- @param v any Condition +--- @param message string Error message to display if condition is false or nil +--- @return any v if not false or nil, otherwise an error is displayed local function assert(v, message) return v or error(message, 0) end +--- @private --- Show a warning message ---- ----@param msg string Message to show ---- ----@private +--- @param msg string Message to show local function warn(msg, ...) - vim.notify_once(string.format(msg, ...), vim.log.levels.WARN, { + vim.notify_once(msg:format(...), vim.log.levels.WARN, { title = 'editorconfig', }) end -function M.properties.charset(bufnr, val) +--- If "true", then stop searching for `.editorconfig` files in parent +--- directories. This property must be at the top-level of the +--- `.editorconfig` file (i.e. it must not be within a glob section). +function properties.root() + -- Unused +end + +--- One of `"utf-8"`, `"utf-8-bom"`, `"latin1"`, `"utf-16be"`, or `"utf-16le"`. +--- Sets the 'fileencoding' and 'bomb' options. +function properties.charset(bufnr, val) assert( vim.list_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"' @@ -40,14 +88,18 @@ function M.properties.charset(bufnr, val) end end -function M.properties.end_of_line(bufnr, val) +--- One of `"lf"`, `"crlf"`, or `"cr"`. +--- These correspond to setting 'fileformat' to "unix", "dos", or "mac", +--- respectively. +function properties.end_of_line(bufnr, val) vim.bo[bufnr].fileformat = assert( ({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val], 'end_of_line must be one of "lf", "crlf", or "cr"' ) end -function M.properties.indent_style(bufnr, val, opts) +--- One of `"tab"` or `"space"`. Sets the 'expandtab' option. +function properties.indent_style(bufnr, val, opts) assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"') vim.bo[bufnr].expandtab = val == 'space' if val == 'tab' and not opts.indent_size then @@ -56,7 +108,11 @@ function M.properties.indent_style(bufnr, val, opts) end end -function M.properties.indent_size(bufnr, val, opts) +--- A number indicating the size of a single indent. Alternatively, use the +--- value "tab" to use the value of the tab_width property. Sets the +--- 'shiftwidth' and 'softtabstop' options. If this value is not "tab" and +--- the tab_width property is not set, 'tabstop' is also set to this value. +function properties.indent_size(bufnr, val, opts) if val == 'tab' then vim.bo[bufnr].shiftwidth = 0 vim.bo[bufnr].softtabstop = 0 @@ -70,11 +126,14 @@ function M.properties.indent_size(bufnr, val, opts) end end -function M.properties.tab_width(bufnr, val) +--- The display size of a single tab character. Sets the 'tabstop' option. +function properties.tab_width(bufnr, val) vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number') end -function M.properties.max_line_length(bufnr, val) +--- A number indicating the maximum length of a single +--- line. Sets the 'textwidth' option. +function properties.max_line_length(bufnr, val) local n = tonumber(val) if n then vim.bo[bufnr].textwidth = n @@ -84,7 +143,8 @@ function M.properties.max_line_length(bufnr, val) end end -function M.properties.trim_trailing_whitespace(bufnr, val) +--- When `"true"`, trailing whitespace is automatically removed when the buffer is written. +function properties.trim_trailing_whitespace(bufnr, val) assert( val == 'true' or val == 'false', 'trim_trailing_whitespace must be either "true" or "false"' @@ -109,7 +169,9 @@ function M.properties.trim_trailing_whitespace(bufnr, val) end end -function M.properties.insert_final_newline(bufnr, val) +--- `"true"` or `"false"` to ensure the file always has a trailing newline as its last byte. +--- Sets the 'fixendofline' and 'endofline' options. +function properties.insert_final_newline(bufnr, val) assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"') vim.bo[bufnr].fixendofline = val == 'true' @@ -128,63 +190,56 @@ function M.properties.insert_final_newline(bufnr, val) end end ---- Modified version of |glob2regpat()| that does not match path separators on *. +--- @private +--- Modified version of [glob2regpat()] that does not match path separators on `*`. --- ---- This function replaces single instances of * with the regex pattern [^/]*. However, the star in ---- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass ---- it through glob2regpat, then replace the placeholder with the actual regex pattern. +--- This function replaces single instances of `*` with the regex pattern `[^/]*`. +--- However, the star in the replacement pattern also gets interpreted by glob2regpat, +--- so we insert a placeholder, pass it through glob2regpat, then replace the +--- placeholder with the actual regex pattern. --- ----@param glob string Glob to convert into a regular expression ----@return string Regular expression ---- ----@private +--- @param glob string Glob to convert into a regular expression +--- @return string regex Regular expression local function glob2regpat(glob) local placeholder = '@@PLACEHOLDER@@' - return ( - string.gsub( - vim.fn.glob2regpat( - vim.fn.substitute( - string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'), - '\\*\\@<!\\*\\*\\@!', - placeholder, - 'g' - ) - ), - placeholder, - '[^/]*' - ) + local glob1 = vim.fn.substitute( + glob:gsub('{(%d+)%.%.(%d+)}', '[%1-%2]'), + '\\*\\@<!\\*\\*\\@!', + placeholder, + 'g' ) + local regpat = vim.fn.glob2regpat(glob1) + return (regpat:gsub(placeholder, '[^/]*')) end +--- @private --- Parse a single line in an EditorConfig file ---- ----@param line string Line ----@return string|nil If the line contains a pattern, the glob pattern ----@return string|nil If the line contains a key-value pair, the key ----@return string|nil If the line contains a key-value pair, the value ---- ----@private +--- @param line string Line +--- @return string? glob pattern if the line contains a pattern +--- @return string? key if the line contains a key-value pair +--- @return string? value if the line contains a key-value pair local function parse_line(line) - if line:find('^%s*[^ #;]') then - local glob = (line:match('%b[]') or ''):match('^%s*%[(.*)%]%s*$') - if glob then - return glob, nil, nil - end + if not line:find('^%s*[^ #;]') then + return + end - local key, val = line:match('^%s*([^:= ][^:=]-)%s*[:=]%s*(.-)%s*$') - if key ~= nil and val ~= nil then - return nil, key:lower(), val:lower() - end + --- @type string? + local glob = (line:match('%b[]') or ''):match('^%s*%[(.*)%]%s*$') + if glob then + return glob + end + + local key, val = line:match('^%s*([^:= ][^:=]-)%s*[:=]%s*(.-)%s*$') + if key ~= nil and val ~= nil then + return nil, key:lower(), val:lower() end end ---- Parse options from an .editorconfig file ---- ----@param filepath string File path of the file to apply EditorConfig settings to ----@param dir string Current directory ----@return table<string,string|boolean> Table of options to apply to the given file ---- ----@private +--- @private +--- Parse options from an `.editorconfig` file +--- @param filepath string File path of the file to apply EditorConfig settings to +--- @param dir string Current directory +--- @return table<string,string|boolean> Table of options to apply to the given file local function parse(filepath, dir) local pat --- @type vim.regex? local opts = {} --- @type table<string,string|boolean> @@ -215,11 +270,14 @@ local function parse(filepath, dir) return opts end ---- Configure the given buffer with options from an .editorconfig file ---- ----@param bufnr integer Buffer number to configure ---- ----@private +local M = {} + +-- Exposed for use in syntax/editorconfig.vim` +M.properties = properties + +--- @private +--- Configure the given buffer with options from an `.editorconfig` file +--- @param bufnr integer Buffer number to configure function M.config(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() if not vim.api.nvim_buf_is_valid(bufnr) then @@ -249,6 +307,7 @@ function M.config(bufnr) if val ~= 'unset' then local func = M.properties[opt] if func then + --- @type boolean, string? local ok, err = pcall(func, bufnr, val, opts) if ok then applied[opt] = val diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index ac15aff60c..02e841030f 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -411,15 +411,13 @@ local function find_man() return false end ----@param pager boolean -local function set_options(pager) +local function set_options() vim.bo.swapfile = false vim.bo.buftype = 'nofile' vim.bo.bufhidden = 'unload' vim.bo.modified = false vim.bo.readonly = true vim.bo.modifiable = false - vim.b.pager = pager vim.bo.filetype = 'man' end @@ -475,7 +473,7 @@ local function put_page(page) vim.cmd([[silent! keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g]]) vim.cmd('1') -- Move cursor to first line highlight_man_page() - set_options(false) + set_options() end local function format_candidate(path, psect) @@ -662,7 +660,8 @@ function M.init_pager() vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } }) end - set_options(true) + vim.g.pager = true + set_options() end ---@param count integer @@ -716,11 +715,11 @@ function M.open_page(count, smods, args) local target = ('%s(%s)'):format(name, sect) local ok, ret = pcall(function() - if smods.tab == -1 and find_man() then - vim.cmd.tag({ target, mods = { silent = true, keepalt = true } }) + smods.silent = true + smods.keepalt = true + if smods.hide or (smods.tab == -1 and find_man()) then + vim.cmd.tag({ target, mods = smods }) else - smods.silent = true - smods.keepalt = true vim.cmd.stag({ target, mods = smods }) end end) @@ -730,7 +729,7 @@ function M.open_page(count, smods, args) if not ok then error(ret) else - set_options(false) + set_options() end vim.b.man_sect = sect diff --git a/runtime/lua/provider/clipboard/health.lua b/runtime/lua/provider/clipboard/health.lua deleted file mode 100644 index dc33cb0ab0..0000000000 --- a/runtime/lua/provider/clipboard/health.lua +++ /dev/null @@ -1,40 +0,0 @@ -local health = vim.health -local executable = health.executable - -local M = {} - -function M.check() - health.start('Clipboard (optional)') - - if - os.getenv('TMUX') - and executable('tmux') - and executable('pbpaste') - and not health.cmd_ok('pbpaste') - then - local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') - local advice = { - 'Install tmux 2.6+. https://superuser.com/q/231130', - 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233', - } - health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) - end - - local clipboard_tool = vim.fn['provider#clipboard#Executable']() - if vim.g.clipboard ~= nil and clipboard_tool == '' then - local error_message = vim.fn['provider#clipboard#Error']() - health.error( - error_message, - "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." - ) - elseif clipboard_tool:find('^%s*$') then - health.warn( - 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', - ':help clipboard' - ) - else - health.ok('Clipboard tool found: ' .. clipboard_tool) - end -end - -return M diff --git a/runtime/lua/provider/node/health.lua b/runtime/lua/provider/node/health.lua deleted file mode 100644 index a434f8a92b..0000000000 --- a/runtime/lua/provider/node/health.lua +++ /dev/null @@ -1,105 +0,0 @@ -local health = vim.health -local executable = health.executable -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' - -local M = {} - -function M.check() - health.start('Node.js provider (optional)') - - if health.provider_disabled('node') then - return - end - - if - not executable('node') - or (not executable('npm') and not executable('yarn') and not executable('pnpm')) - then - health.warn( - '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.', - 'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.' - ) - return - end - - -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' - local ok, node_v = health.cmd_ok({ 'node', '-v' }) - health.info('Node.js: ' .. node_v) - if not ok or vim.version.lt(node_v, '6.0.0') then - health.warn('Nvim node.js host does not support Node ' .. node_v) - -- Skip further checks, they are nonsense if nodejs is too old. - return - end - if vim.fn['provider#node#can_inspect']() == 0 then - health.warn( - 'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.' - ) - end - - local node_detect_table = vim.fn['provider#node#Detect']() - local host = node_detect_table[1] - if host:find('^%s*$') then - health.warn('Missing "neovim" npm (or yarn, pnpm) package.', { - 'Run in shell: npm install -g neovim', - 'Run in shell (if you use yarn): yarn global add neovim', - 'Run in shell (if you use pnpm): pnpm install -g neovim', - 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim', - }) - return - end - health.info('Nvim node.js host: ' .. host) - - local manager = 'npm' - if executable('yarn') then - manager = 'yarn' - elseif executable('pnpm') then - manager = 'pnpm' - end - - local latest_npm_cmd = ( - iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json' - ) - local latest_npm - ok, latest_npm = health.cmd_ok(vim.split(latest_npm_cmd, ' ')) - if not ok or latest_npm:find('^%s$') then - health.error( - 'Failed to run: ' .. latest_npm_cmd, |