diff options
Diffstat (limited to 'runtime/lua')
70 files changed, 4654 insertions, 2478 deletions
diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua index 23c078cb87..29bafc53b2 100644 --- a/runtime/lua/_vim9script.lua +++ b/runtime/lua/_vim9script.lua @@ -145,7 +145,7 @@ local vim9 = (function() -- work well for calling ":source X" from within a vimscript/vim9script -- function M.make_source_cmd = function() - local group = vim.api.nvim_create_augroup('vim9script-source', {}) + local group = vim.api.nvim_create_augroup('nvim.vim9script_source', {}) vim.api.nvim_create_autocmd('SourceCmd', { pattern = '*.vim', group = group, diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua index 6b179f1ef0..43e321eac3 100644 --- a/runtime/lua/coxpcall.lua +++ b/runtime/lua/coxpcall.lua @@ -1,6 +1,10 @@ ------------------------------------------------------------------------------- +-- (Not needed for LuaJIT or Lua 5.2+) +-- -- Coroutine safe xpcall and pcall versions -- +-- https://keplerproject.github.io/coxpcall/ +-- -- Encapsulates the protected calls with a coroutine based loop, so errors can -- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines -- yielding inside the call to pcall or xpcall. @@ -27,22 +31,23 @@ end -- No need to do anything if pcall and xpcall are already safe. if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then - copcall = pcall - coxpcall = xpcall + _G.copcall = pcall + _G.coxpcall = xpcall return { pcall = pcall, xpcall = xpcall, running = coroutine.running } end ------------------------------------------------------------------------------- -- Implements xpcall with coroutines ------------------------------------------------------------------------------- -local performResume, handleReturnValue +local performResume local oldpcall, oldxpcall = pcall, xpcall local pack = table.pack or function(...) return {n = select("#", ...), ...} end local unpack = table.unpack or unpack local running = coroutine.running +--- @type table<thread,thread> local coromap = setmetatable({}, { __mode = "k" }) -function handleReturnValue(err, co, status, ...) +local function handleReturnValue(err, co, status, ...) if not status then return false, err(debug.traceback(co, (...)), ...) end @@ -57,11 +62,12 @@ function performResume(err, co, ...) return handleReturnValue(err, co, coroutine.resume(co, ...)) end +--- @diagnostic disable-next-line: unused-vararg local function id(trace, ...) return trace end -function coxpcall(f, err, ...) +function _G.coxpcall(f, err, ...) local current = running() if not current then if err == id then @@ -84,6 +90,7 @@ function coxpcall(f, err, ...) end end +--- @param coro? thread local function corunning(coro) if coro ~= nil then assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro)) @@ -101,7 +108,7 @@ end -- Implements pcall with coroutines ------------------------------------------------------------------------------- -function copcall(f, ...) +function _G.copcall(f, ...) return coxpcall(f, id, ...) end diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index e65d267a70..2093c4eb5c 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -151,7 +151,7 @@ function properties.trim_trailing_whitespace(bufnr, val) ) if val == 'true' then vim.api.nvim_create_autocmd('BufWritePre', { - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, callback = function() local view = vim.fn.winsaveview() @@ -163,7 +163,7 @@ function properties.trim_trailing_whitespace(bufnr, val) else vim.api.nvim_clear_autocmds({ event = 'BufWritePre', - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, }) end @@ -180,7 +180,7 @@ function properties.insert_final_newline(bufnr, val) local endofline = val == 'true' if vim.bo[bufnr].endofline ~= endofline then vim.api.nvim_create_autocmd('BufWritePre', { - group = 'editorconfig', + group = 'nvim.editorconfig', buffer = bufnr, once = true, callback = function() diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 114c94f9e5..96cfdf523d 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,93 +1,84 @@ local api, fn = vim.api, vim.fn -local FIND_ARG = '-w' -local localfile_arg = true -- Always use -l if possible. #6683 - ----@type table[] -local buf_hls = {} - local M = {} -local function man_error(msg) - M.errormsg = 'man.lua: ' .. vim.inspect(msg) - error(M.errormsg) -end - --- Run a system command and timeout after 30 seconds. ----@param cmd string[] ----@param silent boolean? ----@param env? table<string,string|number> ----@return string +--- Run a system command and timeout after 10 seconds. +--- @param cmd string[] +--- @param silent boolean? +--- @param env? table<string,string|number> +--- @return string local function system(cmd, silent, env) local r = vim.system(cmd, { env = env, timeout = 10000 }):wait() - if r.code ~= 0 and not silent then - local cmd_str = table.concat(cmd, ' ') - man_error(string.format("command error '%s': %s", cmd_str, r.stderr)) + if not silent then + if r.code ~= 0 then + local cmd_str = table.concat(cmd, ' ') + error(string.format("command error '%s': %s", cmd_str, r.stderr)) + end + assert(r.stdout ~= '') end return assert(r.stdout) end ----@param line string ----@param linenr integer -local function highlight_line(line, linenr) - ---@type string[] +--- @enum Man.Attribute +local Attrs = { + None = 0, + Bold = 1, + Underline = 2, + Italic = 3, +} + +--- @param line string +--- @param row integer +--- @param hls {attr:Man.Attribute,row:integer,start:integer,final:integer}[] +--- @return string +local function render_line(line, row, hls) + --- @type string[] local chars = {} local prev_char = '' local overstrike, escape, osc8 = false, false, false - ---@type table<integer,{attr:integer,start:integer,final:integer}> - local hls = {} -- Store highlight groups as { attr, start, final } - - local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 - local hl_groups = { [BOLD] = 'manBold', [UNDERLINE] = 'manUnderline', [ITALIC] = 'manItalic' } - local attr = NONE + local attr = Attrs.None local byte = 0 -- byte offset - local function end_attr_hl(attr_) - for i, hl in ipairs(hls) do - if hl.attr == attr_ and hl.final == -1 then - hl.final = byte - hls[i] = hl - end - end - end + local hls_start = #hls + 1 + --- @param code integer local function add_attr_hl(code) local continue_hl = true if code == 0 then - attr = NONE + attr = Attrs.None continue_hl = false elseif code == 1 then - attr = BOLD + attr = Attrs.Bold elseif code == 22 then - attr = BOLD + attr = Attrs.Bold continue_hl = false elseif code == 3 then - attr = ITALIC + attr = Attrs.Italic elseif code == 23 then - attr = ITALIC + attr = Attrs.Italic continue_hl = false elseif code == 4 then - attr = UNDERLINE + attr = Attrs.Underline elseif code == 24 then - attr = UNDERLINE + attr = Attrs.Underline continue_hl = false else - attr = NONE + attr = Attrs.None return end if continue_hl then - hls[#hls + 1] = { attr = attr, start = byte, final = -1 } + hls[#hls + 1] = { attr = attr, row = row, start = byte, final = -1 } else - if attr == NONE then - for a, _ in pairs(hl_groups) do - end_attr_hl(a) + for _, a in pairs(attr == Attrs.None and Attrs or { attr }) do + for i = hls_start, #hls do + if hls[i].attr == a and hls[i].final == -1 then + hls[i].final = byte + end end - else - end_attr_hl(attr) end end end @@ -100,11 +91,11 @@ local function highlight_line(line, linenr) if overstrike then local last_hl = hls[#hls] if char == prev_char then - if char == '_' and attr == ITALIC and last_hl and last_hl.final == byte then + if char == '_' and attr == Attrs.Italic and last_hl and last_hl.final == byte then -- This underscore is in the middle of an italic word - attr = ITALIC + attr = Attrs.Italic else - attr = BOLD + attr = Attrs.Bold end elseif prev_char == '_' then -- Even though underline is strictly what this should be. <bs>_ was used by nroff to @@ -113,26 +104,26 @@ local function highlight_line(line, linenr) -- See: -- - https://unix.stackexchange.com/questions/274658/purpose-of-ascii-text-with-overstriking-file-format/274795#274795 -- - https://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm - -- attr = UNDERLINE - attr = ITALIC + -- attr = Attrs.Underline + attr = Attrs.Italic elseif prev_char == '+' and char == 'o' then -- bullet (overstrike text '+^Ho') - attr = BOLD + attr = Attrs.Bold char = '·' elseif prev_char == '·' and char == 'o' then -- bullet (additional handling for '+^H+^Ho^Ho') - attr = BOLD + attr = Attrs.Bold char = '·' else -- use plain char - attr = NONE + attr = Attrs.None end -- Grow the previous highlight group if possible if last_hl and last_hl.attr == attr and last_hl.final == byte then last_hl.final = byte + #char else - hls[#hls + 1] = { attr = attr, start = byte, final = byte + #char } + hls[#hls + 1] = { attr = attr, row = row, start = byte, final = byte + #char } end overstrike = false @@ -151,14 +142,15 @@ local function highlight_line(line, linenr) -- We only want to match against SGR sequences, which consist of ESC -- followed by '[', then a series of parameter and intermediate bytes in -- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117) - ---@type string? + --- @type string? local sgr = prev_char:match('^%[([\032-\063]*)m$') -- Ignore escape sequences with : characters, as specified by ITU's T.416 -- Open Document Architecture and interchange format. - if sgr and not string.find(sgr, ':') then - local match ---@type string? + if sgr and not sgr:find(':') then + local match --- @type string? while sgr and #sgr > 0 do -- Match against SGR parameters, which may be separated by ';' + --- @type string?, string? match, sgr = sgr:match('^(%d*);?(.*)') add_attr_hl(match + 0) -- coerce to number end @@ -184,55 +176,43 @@ local function highlight_line(line, linenr) end end - for _, hl in ipairs(hls) do - if hl.attr ~= NONE then - buf_hls[#buf_hls + 1] = { - 0, - -1, - hl_groups[hl.attr], - linenr - 1, - hl.start, - hl.final, - } - end - end - return table.concat(chars, '') end +local HlGroups = { + [Attrs.Bold] = 'manBold', + [Attrs.Underline] = 'manUnderline', + [Attrs.Italic] = 'manItalic', +} + local function highlight_man_page() local mod = vim.bo.modifiable vim.bo.modifiable = true local lines = api.nvim_buf_get_lines(0, 0, -1, false) + + --- @type {attr:Man.Attribute,row:integer,start:integer,final:integer}[] + local hls = {} + for i, line in ipairs(lines) do - lines[i] = highlight_line(line, i) + lines[i] = render_line(line, i - 1, hls) end + api.nvim_buf_set_lines(0, 0, -1, false, lines) - for _, args in ipairs(buf_hls) do - api.nvim_buf_add_highlight(unpack(args)) + for _, hl in ipairs(hls) do + if hl.attr ~= Attrs.None then + --- @diagnostic disable-next-line: deprecated + api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final) + end end - buf_hls = {} vim.bo.modifiable = mod end --- replace spaces in a man page name with underscores --- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)'; --- while editing SQL source code, it's nice to visually select 'CREATE TABLE' --- and hit 'K', which requires this transformation ----@param str string ----@return string -local function spaces_to_underscores(str) - local res = str:gsub('%s', '_') - return res -end - ----@param sect string|nil ----@param name string|nil ----@param silent boolean -local function get_path(sect, name, silent) +--- @param name? string +--- @param sect? string +local function get_path(name, sect) name = name or '' sect = sect or '' -- Some man implementations (OpenBSD) return all available paths from the @@ -253,14 +233,14 @@ local function get_path(sect, name, silent) -- -- Finally, we can avoid relying on -S or -s here since they are very -- inconsistently supported. Instead, call -w with a section and a name. - local cmd ---@type string[] + local cmd --- @type string[] if sect == '' then - cmd = { 'man', FIND_ARG, name } + cmd = { 'man', '-w', name } else - cmd = { 'man', FIND_ARG, sect, name } + cmd = { 'man', '-w', sect, name } end - local lines = system(cmd, silent) + local lines = system(cmd, true) local results = vim.split(lines, '\n', { trimempty = true }) if #results == 0 then @@ -276,87 +256,73 @@ local function get_path(sect, name, silent) end -- find any that match the specified name - ---@param v string + --- @param v string local namematches = vim.tbl_filter(function(v) local tail = fn.fnamemodify(v, ':t') - return string.find(tail, name, 1, true) + return tail:find(name, 1, true) ~= nil end, results) or {} local sectmatches = {} if #namematches > 0 and sect ~= '' then - ---@param v string + --- @param v string sectmatches = vim.tbl_filter(function(v) return fn.fnamemodify(v, ':e') == sect end, namematches) end - return fn.substitute(sectmatches[1] or namematches[1] or results[1], [[\n\+$]], '', '') + return (sectmatches[1] or namematches[1] or results[1]):gsub('\n+$', '') end ----@param text string ----@param pat_or_re string -local function matchstr(text, pat_or_re) - local re = type(pat_or_re) == 'string' and vim.regex(pat_or_re) or pat_or_re - - ---@type integer, integer - local s, e = re:match_str(text) - - if s == nil then - return +--- Attempt to extract the name and sect out of 'name(sect)' +--- otherwise just return the largest string of valid characters in ref +--- @param ref string +--- @return string? name +--- @return string? sect +--- @return string? err +local function parse_ref(ref) + if ref == '' or ref:sub(1, 1) == '-' then + return nil, nil, ('invalid manpage reference "%s"'):format(ref) end - return text:sub(vim.str_utfindex(text, 'utf-32', s) + 1, vim.str_utfindex(text, 'utf-32', e)) -end + -- match "<name>(<sect>)" + -- note: name can contain spaces + local name, sect = ref:match('([^()]+)%(([^()]+)%)') + if name then + -- see ':Man 3X curses' on why tolower. + -- TODO(nhooyr) Not sure if this is portable across OSs + -- but I have not seen a single uppercase section. + return name, sect:lower() + end --- attempt to extract the name and sect out of 'name(sect)' --- otherwise just return the largest string of valid characters in ref ----@param ref string ----@return string, string -local function extract_sect_and_name_ref(ref) - ref = ref or '' - if ref:sub(1, 1) == '-' then -- try ':Man -pandoc' with this disabled. - man_error("manpage name cannot start with '-'") - end - local ref1 = ref:match('[^()]+%([^()]+%)') - if not ref1 then - local name = ref:match('[^()]+') - if not name then - man_error('manpage reference cannot contain only parentheses: ' .. ref) - end - return '', name - end - local parts = vim.split(ref1, '(', { plain = true }) - -- see ':Man 3X curses' on why tolower. - -- TODO(nhooyr) Not sure if this is portable across OSs - -- but I have not seen a single uppercase section. - local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower() - local name = parts[1] - return sect, name + name = ref:match('[^()]+') + if not name then + return nil, nil, ('invalid manpage reference "%s"'):format(ref) + end + return name end --- find_path attempts to find the path to a manpage --- based on the passed section and name. --- --- 1. If manpage could not be found with the given sect and name, --- then try all the sections in b:man_default_sects. --- 2. If it still could not be found, then we try again without a section. --- 3. If still not found but $MANSECT is set, then we try again with $MANSECT --- unset. --- 4. If a path still wasn't found, return nil. ----@param sect string? ----@param name string -function M.find_path(sect, name) +--- Attempts to find the path to a manpage based on the passed section and name. +--- +--- 1. If manpage could not be found with the given sect and name, +--- then try all the sections in b:man_default_sects. +--- 2. If it still could not be found, then we try again without a section. +--- 3. If still not found but $MANSECT is set, then we try again with $MANSECT +--- unset. +--- 4. If a path still wasn't found, return nil. +--- @param name string? +--- @param sect string? +--- @return string? path +function M._find_path(name, sect) if sect and sect ~= '' then - local ret = get_path(sect, name, true) + local ret = |