Compare commits

...

7 Commits

Author SHA1 Message Date
dundargoc
0b7f87a2a9
Merge 551cb8712f into 7121983c45 2024-12-18 14:40:43 +00:00
Lewis Russell
7121983c45 refactor(man.lua): various changes
- Replace all uses of vim.regex with simpler Lua patterns.
- Replace all uses of vim.fn.substitute with string.gsub.
- Rework error handling so expected errors are passed back via a return.
  - These get routed up an passed to `vim.notify()`
  - Any other errors will cause a stack trace.
- Reworked the module initialization of `localfile_arg`
- Updated all type annotations.
- Refactored CLI completion by introduction a parse_cmdline()
  function.
- Simplified `show_toc()`
- Refactor highlighting
- Inline some functions
- Fix completion on MacOS 13 and earlier.
  - Prefer `manpath -q` over `man -w`
- Make completion more efficient by avoiding vim.fn.sort and vim.fn.uniq
  - Reimplement using a single loop
2024-12-18 14:40:36 +00:00
phanium
888a803755
fix(lsp): vim.lsp.start fails if existing client has no workspace_folders #31608
Problem:
regression since https://github.com/neovim/neovim/pull/31340

`nvim -l repro.lua`:
```lua
vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' }
vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' }

-- swapped case will be ok:
-- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' }
-- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' }
```

Failure:
```
E5113: Error while calling lua chunk: /…/lua/vim/lsp.lua:214: bad argument #1 to
'ipairs' (table expected, got nil)
stack traceback:
        [C]: in function 'ipairs'
        /…/lua/vim/lsp.lua:214: in function 'reuse_client'
        /…/lua/vim/lsp.lua:629: in function 'start'
        repro.lua:34: in main chunk
```
2024-12-18 06:37:12 -08:00
Peter Lithammer
07d5dc8938
feat(lsp): show server version in :checkhealth #31611
Problem:
Language server version information missing from `:checkhealth vim.lsp`.

Solution:
Store `InitializeResult.serverInfo.version` from the `initialize`
response and display for each client in `:checkhealth vim.lsp`.
2024-12-18 06:31:25 -08:00
Justin M. Keyes
f9eb68f340
fix(coverity): error handling CHECKED_RETURN #31618
CID 516406:  Error handling issues  (CHECKED_RETURN)
    /src/nvim/api/vimscript.c: 284 in nvim_call_dict_function()
    278       Object rv = OBJECT_INIT;
    279
    280       typval_T rettv;
    281       bool mustfree = false;
    282       switch (dict.type) {
    283       case kObjectTypeString:
    >>>     CID 516406:  Error handling issues  (CHECKED_RETURN)
    >>>     Calling "eval0" without checking return value (as is done elsewhere 10 out of 12 times).
    284         TRY_WRAP(err, {
    285           eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
    286           clear_evalarg(&EVALARG_EVALUATE, NULL);
    287         });
    288         if (ERROR_SET(err)) {
    289           return rv;
2024-12-18 06:05:37 -08:00
zeertzjq
738320188f
test(old): fix incorrect comment in test_preview.vim (#31619) 2024-12-18 10:21:52 +08:00
dundargoc
551cb8712f docs: misc
Co-authored-by: Axel <axelhjq@gmail.com>
Co-authored-by: Colin Kennedy <colinvfx@gmail.com>
Co-authored-by: Juan Giordana <juangiordana@gmail.com>
Co-authored-by: Yochem van Rosmalen <git@yochem.nl>
Co-authored-by: ifish <fishioon@live.com>
2024-12-13 14:21:12 +01:00
21 changed files with 427 additions and 413 deletions

View File

@ -131,7 +131,8 @@ https://github.com/cascent/neovim-cygwin was built on Cygwin 2.9.0. Newer `libuv
1. From the MSYS2 shell, install these packages: 1. From the MSYS2 shell, install these packages:
``` ```
pacman -S \ pacman -S \
mingw-w64-ucrt-x86_64-{gcc,cmake,make,ninja,diffutils} mingw-w64-ucrt-x86_64-gcc \
mingw-w64-x86_64-{cmake,make,ninja,diffutils}
``` ```
2. From the Windows Command Prompt (`cmd.exe`), set up the `PATH` and build. 2. From the Windows Command Prompt (`cmd.exe`), set up the `PATH` and build.
@ -292,13 +293,13 @@ Platform-specific requirements are listed below.
### Ubuntu / Debian ### Ubuntu / Debian
```sh ```sh
sudo apt-get install ninja-build gettext cmake unzip curl build-essential sudo apt-get install ninja-build gettext cmake curl build-essential
``` ```
### RHEL / Fedora ### RHEL / Fedora
``` ```
sudo dnf -y install ninja-build cmake gcc make unzip gettext curl glibc-gconv-extra sudo dnf -y install ninja-build cmake gcc make gettext curl glibc-gconv-extra
``` ```
### openSUSE ### openSUSE
@ -310,13 +311,13 @@ sudo zypper install ninja cmake gcc-c++ gettext-tools curl
### Arch Linux ### Arch Linux
``` ```
sudo pacman -S base-devel cmake unzip ninja curl sudo pacman -S base-devel cmake ninja curl
``` ```
### Alpine Linux ### Alpine Linux
``` ```
apk add build-base cmake coreutils curl unzip gettext-tiny-dev apk add build-base cmake coreutils curl gettext-tiny-dev
``` ```
### Void Linux ### Void Linux
@ -380,7 +381,7 @@ or a specific SHA1 like `--override-input neovim-src github:neovim/neovim/89dc8f
### FreeBSD ### FreeBSD
``` ```
sudo pkg install cmake gmake sha unzip wget gettext curl sudo pkg install cmake gmake sha wget gettext curl
``` ```
If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 hash is `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, then this is your issue (that's the `sha256sum` of an empty file). If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 hash is `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, then this is your issue (that's the `sha256sum` of an empty file).
@ -388,7 +389,7 @@ If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 h
### OpenBSD ### OpenBSD
```sh ```sh
doas pkg_add gmake cmake unzip curl gettext-tools doas pkg_add gmake cmake curl gettext-tools
``` ```
Build can sometimes fail when using the top level `Makefile`, apparently due to some third-party component (see [#2445-comment](https://github.com/neovim/neovim/issues/2445#issuecomment-108124236)). The following instructions use CMake: Build can sometimes fail when using the top level `Makefile`, apparently due to some third-party component (see [#2445-comment](https://github.com/neovim/neovim/issues/2445#issuecomment-108124236)). The following instructions use CMake:

View File

@ -133,6 +133,9 @@ generated-sources benchmark $(FORMAT) $(LINT) $(TEST) doc: | build/.ran-cmake
test: $(TEST) test: $(TEST)
# iwyu-fix-includes can be downloaded from
# https://github.com/include-what-you-use/include-what-you-use/blob/master/fix_includes.py.
# Create a iwyu-fix-includes shell script in your $PATH that invokes the python script.
iwyu: build/.ran-cmake iwyu: build/.ran-cmake
$(CMAKE) --preset iwyu $(CMAKE) --preset iwyu
$(CMAKE) --build build > build/iwyu.log $(CMAKE) --build build > build/iwyu.log

View File

@ -32,7 +32,7 @@ Follow these steps to get LSP features:
Example: >lua Example: >lua
vim.lsp.config['luals'] = { vim.lsp.config['luals'] = {
-- Command and arguments to start the server. -- Command and arguments to start the server.
cmd = { 'lua-language-server' } cmd = { 'lua-language-server' },
-- Filetypes to automatically attach to. -- Filetypes to automatically attach to.
filetypes = { 'lua' }, filetypes = { 'lua' },
@ -97,7 +97,7 @@ Given: >lua
multilineTokenSupport = true, multilineTokenSupport = true,
} }
} }
} },
root_markers = { '.git' }, root_markers = { '.git' },
}) })
@ -885,7 +885,7 @@ foldexpr({lnum}) *vim.lsp.foldexpr()*
To use, check for the "textDocument/foldingRange" capability in an To use, check for the "textDocument/foldingRange" capability in an
|LspAttach| autocommand. Example: >lua |LspAttach| autocommand. Example: >lua
vim.api.nvim_create_autocommand('LspAttach', { vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args) callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id) local client = vim.lsp.get_client_by_id(args.data.client_id)
if client:supports_method('textDocument/foldingRange') then if client:supports_method('textDocument/foldingRange') then
@ -1126,6 +1126,9 @@ Lua module: vim.lsp.client *lsp-client*
• {server_capabilities} (`lsp.ServerCapabilities?`) Response from the • {server_capabilities} (`lsp.ServerCapabilities?`) Response from the
server sent on `initialize` describing the server sent on `initialize` describing the
server's capabilities. server's capabilities.
• {server_info} (`lsp.ServerInfo?`) Response from the server
sent on `initialize` describing information
about the server.
• {progress} (`vim.lsp.Client.Progress`) A ring buffer • {progress} (`vim.lsp.Client.Progress`) A ring buffer
(|vim.ringbuf()|) containing progress messages (|vim.ringbuf()|) containing progress messages
sent by the server. See sent by the server. See

View File

@ -115,6 +115,7 @@ LSP
• |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()| • |vim.lsp.util.make_position_params()|, |vim.lsp.util.make_range_params()|
and |vim.lsp.util.make_given_range_params()| now require the `position_encoding` and |vim.lsp.util.make_given_range_params()| now require the `position_encoding`
parameter. parameter.
• `:checkhealth vim.lsp` displays the server version (if available).
LUA LUA

View File

@ -1560,8 +1560,8 @@ A jump table for the options with a short description can be found at |Q_op|.
"menu" or "menuone". No effect if "longest" is present. "menu" or "menuone". No effect if "longest" is present.
noselect Same as "noinsert", except that no menu item is noselect Same as "noinsert", except that no menu item is
pre-selected. If both "noinsert" and "noselect" are present, pre-selected. If both "noinsert" and "noselect" are
"noselect" has precedence. present, "noselect" has precedence.
fuzzy Enable |fuzzy-matching| for completion candidates. This fuzzy Enable |fuzzy-matching| for completion candidates. This
allows for more flexible and intuitive matching, where allows for more flexible and intuitive matching, where

View File

@ -70,7 +70,7 @@ adds arbitrary metadata and conditional data to a match.
Queries are written in a lisp-like language documented in Queries are written in a lisp-like language documented in
https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
Note: The predicates listed there page differ from those Nvim supports. See Note: The predicates listed there differ from those Nvim supports. See
|treesitter-predicates| for a complete list of predicates supported by Nvim. |treesitter-predicates| for a complete list of predicates supported by Nvim.
Nvim looks for queries as `*.scm` files in a `queries` directory under Nvim looks for queries as `*.scm` files in a `queries` directory under

View File

@ -1,19 +1,8 @@
local api, fn = vim.api, vim.fn 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 M = {}
local function man_error(msg) --- Run a system command and timeout after 10 seconds.
M.errormsg = 'man.lua: ' .. vim.inspect(msg)
error(M.errormsg)
end
-- Run a system command and timeout after 30 seconds.
--- @param cmd string[] --- @param cmd string[]
--- @param silent boolean? --- @param silent boolean?
--- @param env? table<string,string|number> --- @param env? table<string,string|number>
@ -24,7 +13,7 @@ local function system(cmd, silent, env)
if not silent then if not silent then
if r.code ~= 0 then if r.code ~= 0 then
local cmd_str = table.concat(cmd, ' ') local cmd_str = table.concat(cmd, ' ')
man_error(string.format("command error '%s': %s", cmd_str, r.stderr)) error(string.format("command error '%s': %s", cmd_str, r.stderr))
end end
assert(r.stdout ~= '') assert(r.stdout ~= '')
end end
@ -32,65 +21,64 @@ local function system(cmd, silent, env)
return assert(r.stdout) return assert(r.stdout)
end end
--- @enum Man.Attribute
local Attrs = {
None = 0,
Bold = 1,
Underline = 2,
Italic = 3,
}
--- @param line string --- @param line string
---@param linenr integer --- @param row integer
local function highlight_line(line, linenr) --- @param hls {attr:Man.Attribute,row:integer,start:integer,final:integer}[]
--- @return string
local function render_line(line, row, hls)
--- @type string[] --- @type string[]
local chars = {} local chars = {}
local prev_char = '' local prev_char = ''
local overstrike, escape, osc8 = false, false, false local overstrike, escape, osc8 = false, false, false
---@type table<integer,{attr:integer,start:integer,final:integer}> local attr = Attrs.None
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 byte = 0 -- byte offset local byte = 0 -- byte offset
local function end_attr_hl(attr_) local hls_start = #hls + 1
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
--- @param code integer
local function add_attr_hl(code) local function add_attr_hl(code)
local continue_hl = true local continue_hl = true
if code == 0 then if code == 0 then
attr = NONE attr = Attrs.None
continue_hl = false continue_hl = false
elseif code == 1 then elseif code == 1 then
attr = BOLD attr = Attrs.Bold
elseif code == 22 then elseif code == 22 then
attr = BOLD attr = Attrs.Bold
continue_hl = false continue_hl = false
elseif code == 3 then elseif code == 3 then
attr = ITALIC attr = Attrs.Italic
elseif code == 23 then elseif code == 23 then
attr = ITALIC attr = Attrs.Italic
continue_hl = false continue_hl = false
elseif code == 4 then elseif code == 4 then
attr = UNDERLINE attr = Attrs.Underline
elseif code == 24 then elseif code == 24 then
attr = UNDERLINE attr = Attrs.Underline
continue_hl = false continue_hl = false
else else
attr = NONE attr = Attrs.None
return return
end end
if continue_hl then 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 else
if attr == NONE then for _, a in pairs(attr == Attrs.None and Attrs or { attr }) do
for a, _ in pairs(hl_groups) do for i = hls_start, #hls do
end_attr_hl(a) if hls[i].attr == a and hls[i].final == -1 then
hls[i].final = byte
end
end end
else
end_attr_hl(attr)
end end
end end
end end
@ -103,11 +91,11 @@ local function highlight_line(line, linenr)
if overstrike then if overstrike then
local last_hl = hls[#hls] local last_hl = hls[#hls]
if char == prev_char then 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 -- This underscore is in the middle of an italic word
attr = ITALIC attr = Attrs.Italic
else else
attr = BOLD attr = Attrs.Bold
end end
elseif prev_char == '_' then elseif prev_char == '_' then
-- Even though underline is strictly what this should be. <bs>_ was used by nroff to -- Even though underline is strictly what this should be. <bs>_ was used by nroff to
@ -116,26 +104,26 @@ local function highlight_line(line, linenr)
-- See: -- See:
-- - https://unix.stackexchange.com/questions/274658/purpose-of-ascii-text-with-overstriking-file-format/274795#274795 -- - 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 -- - https://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm
-- attr = UNDERLINE -- attr = Attrs.Underline
attr = ITALIC attr = Attrs.Italic
elseif prev_char == '+' and char == 'o' then elseif prev_char == '+' and char == 'o' then
-- bullet (overstrike text '+^Ho') -- bullet (overstrike text '+^Ho')
attr = BOLD attr = Attrs.Bold
char = '·' char = '·'
elseif prev_char == '·' and char == 'o' then elseif prev_char == '·' and char == 'o' then
-- bullet (additional handling for '+^H+^Ho^Ho') -- bullet (additional handling for '+^H+^Ho^Ho')
attr = BOLD attr = Attrs.Bold
char = '·' char = '·'
else else
-- use plain char -- use plain char
attr = NONE attr = Attrs.None
end end
-- Grow the previous highlight group if possible -- Grow the previous highlight group if possible
if last_hl and last_hl.attr == attr and last_hl.final == byte then if last_hl and last_hl.attr == attr and last_hl.final == byte then
last_hl.final = byte + #char last_hl.final = byte + #char
else else
hls[#hls + 1] = { attr = attr, start = byte, final = byte + #char } hls[#hls + 1] = { attr = attr, row = row, start = byte, final = byte + #char }
end end
overstrike = false overstrike = false
@ -158,10 +146,11 @@ local function highlight_line(line, linenr)
local sgr = prev_char:match('^%[([\032-\063]*)m$') local sgr = prev_char:match('^%[([\032-\063]*)m$')
-- Ignore escape sequences with : characters, as specified by ITU's T.416 -- Ignore escape sequences with : characters, as specified by ITU's T.416
-- Open Document Architecture and interchange format. -- Open Document Architecture and interchange format.
if sgr and not string.find(sgr, ':') then if sgr and not sgr:find(':') then
local match --- @type string? local match --- @type string?
while sgr and #sgr > 0 do while sgr and #sgr > 0 do
-- Match against SGR parameters, which may be separated by ';' -- Match against SGR parameters, which may be separated by ';'
--- @type string?, string?
match, sgr = sgr:match('^(%d*);?(.*)') match, sgr = sgr:match('^(%d*);?(.*)')
add_attr_hl(match + 0) -- coerce to number add_attr_hl(match + 0) -- coerce to number
end end
@ -187,55 +176,40 @@ local function highlight_line(line, linenr)
end end
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, '') return table.concat(chars, '')
end end
local HlGroups = {
[Attrs.Bold] = 'manBold',
[Attrs.Underline] = 'manUnderline',
[Attrs.Italic] = 'manItalic',
}
local function highlight_man_page() local function highlight_man_page()
local mod = vim.bo.modifiable local mod = vim.bo.modifiable
vim.bo.modifiable = true vim.bo.modifiable = true
local lines = api.nvim_buf_get_lines(0, 0, -1, false) 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 for i, line in ipairs(lines) do
lines[i] = highlight_line(line, i) lines[i] = render_line(line, i - 1, hls)
end end
api.nvim_buf_set_lines(0, 0, -1, false, lines) api.nvim_buf_set_lines(0, 0, -1, false, lines)
for _, args in ipairs(buf_hls) do for _, hl in ipairs(hls) do
api.nvim_buf_add_highlight(unpack(args)) api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final)
end end
buf_hls = {}
vim.bo.modifiable = mod vim.bo.modifiable = mod
end end
-- replace spaces in a man page name with underscores --- @param name? string
-- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)'; --- @param sect? string
-- while editing SQL source code, it's nice to visually select 'CREATE TABLE' local function get_path(name, sect)
-- 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)
name = name or '' name = name or ''
sect = sect or '' sect = sect or ''
-- Some man implementations (OpenBSD) return all available paths from the -- Some man implementations (OpenBSD) return all available paths from the
@ -258,12 +232,12 @@ local function get_path(sect, name, silent)
-- inconsistently supported. Instead, call -w with a section and a name. -- inconsistently supported. Instead, call -w with a section and a name.
local cmd --- @type string[] local cmd --- @type string[]
if sect == '' then if sect == '' then
cmd = { 'man', FIND_ARG, name } cmd = { 'man', '-w', name }
else else
cmd = { 'man', FIND_ARG, sect, name } cmd = { 'man', '-w', sect, name }
end end
local lines = system(cmd, silent) local lines = system(cmd, true)
local results = vim.split(lines, '\n', { trimempty = true }) local results = vim.split(lines, '\n', { trimempty = true })
if #results == 0 then if #results == 0 then
@ -282,7 +256,7 @@ local function get_path(sect, name, silent)
--- @param v string --- @param v string
local namematches = vim.tbl_filter(function(v) local namematches = vim.tbl_filter(function(v)
local tail = fn.fnamemodify(v, ':t') local tail = fn.fnamemodify(v, ':t')
return string.find(tail, name, 1, true) return tail:find(name, 1, true) ~= nil
end, results) or {} end, results) or {}
local sectmatches = {} local sectmatches = {}
@ -293,73 +267,59 @@ local function get_path(sect, name, silent)
end, namematches) end, namematches)
end 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 end
---@param text string --- Attempt to extract the name and sect out of 'name(sect)'
---@param pat_or_re string --- otherwise just return the largest string of valid characters in ref
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
end
return text:sub(vim.str_utfindex(text, 'utf-32', s) + 1, vim.str_utfindex(text, 'utf-32', e))
end
-- attempt to extract the name and sect out of 'name(sect)'
-- otherwise just return the largest string of valid characters in ref
--- @param ref string --- @param ref string
---@return string, string --- @return string? name
local function extract_sect_and_name_ref(ref) --- @return string? sect
ref = ref or '' --- @return string? err
if ref:sub(1, 1) == '-' then -- try ':Man -pandoc' with this disabled. local function parse_ref(ref)
man_error("manpage name cannot start with '-'") if ref == '' or ref:sub(1, 1) == '-' then
return nil, nil, ('invalid manpage reference "%s"'):format(ref)
end end
local ref1 = ref:match('[^()]+%([^()]+%)')
if not ref1 then -- match "<name>(<sect>)"
local name = ref:match('[^()]+') -- note: name can contain spaces
if not name then local name, sect = ref:match('([^()]+)%(([^()]+)%)')
man_error('manpage reference cannot contain only parentheses: ' .. ref) if name then
end
return '', name
end
local parts = vim.split(ref1, '(', { plain = true })
-- see ':Man 3X curses' on why tolower. -- see ':Man 3X curses' on why tolower.
-- TODO(nhooyr) Not sure if this is portable across OSs -- TODO(nhooyr) Not sure if this is portable across OSs
-- but I have not seen a single uppercase section. -- but I have not seen a single uppercase section.
local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower() return name, sect:lower()
local name = parts[1]
return sect, name
end end
-- find_path attempts to find the path to a manpage name = ref:match('[^()]+')
-- based on the passed section and name. if not name then
-- return nil, nil, ('invalid manpage reference "%s"'):format(ref)
-- 1. If manpage could not be found with the given sect and name, end
-- then try all the sections in b:man_default_sects. return name
-- 2. If it still could not be found, then we try again without a section. end
-- 3. If still not found but $MANSECT is set, then we try again with $MANSECT
-- unset. --- Attempts to find the path to a manpage based on the passed section and name.
-- 4. If a path still wasn't found, return nil. ---
--- 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? --- @param sect string?
---@param name string --- @return string? path
function M.find_path(sect, name) function M._find_path(name, sect)
if sect and sect ~= '' then if sect and sect ~= '' then
local ret = get_path(sect, name, true) local ret = get_path(name, sect)
if ret then if ret then
return ret return ret
end end
end end
if vim.b.man_default_sects ~= nil then if vim.b.man_default_sects ~= nil then
local sects = vim.split(vim.b.man_default_sects, ',', { plain = true, trimempty = true }) for sec in vim.gsplit(vim.b.man_default_sects, ',', { trimempty = true }) do
for _, sec in ipairs(sects) do local ret = get_path(name, sec)
local ret = get_path(sec, name, true)
if ret then if ret then
return ret return ret
end end
@ -367,17 +327,18 @@ function M.find_path(sect, name)
end end
-- if none of the above worked, we will try with no section -- if none of the above worked, we will try with no section
local res_empty_sect = get_path('', name, true) local ret = get_path(name)
if res_empty_sect then if ret then
return res_empty_sect return ret
end end
-- if that still didn't work, we will check for $MANSECT and try again with it -- if that still didn't work, we will check for $MANSECT and try again with it
-- unset -- unset
if vim.env.MANSECT then if vim.env.MANSECT then
--- @type string
local mansect = vim.env.MANSECT local mansect = vim.env.MANSECT
vim.env.MANSECT = nil vim.env.MANSECT = nil
local res = get_path('', name, true) local res = get_path(name)
vim.env.MANSECT = mansect vim.env.MANSECT = mansect
if res then if res then
return res return res
@ -388,21 +349,24 @@ function M.find_path(sect, name)
return nil return nil
end end
local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]]) --- Extracts the name/section from the 'path/name.sect', because sometimes the
--- actual section is more specific than what we provided to `man`
-- Extracts the name/section from the 'path/name.sect', because sometimes the actual section is --- (try `:Man 3 App::CLI`). Also on linux, name seems to be case-insensitive.
-- more specific than what we provided to `man` (try `:Man 3 App::CLI`). --- So for `:Man PRIntf`, we still want the name of the buffer to be 'printf'.
-- Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we
-- still want the name of the buffer to be 'printf'.
--- @param path string --- @param path string
---@return string, string --- @return string name
local function extract_sect_and_name_path(path) --- @return string sect
local function parse_path(path)
local tail = fn.fnamemodify(path, ':t') local tail = fn.fnamemodify(path, ':t')
if EXT_RE:match_str(path) then -- valid extensions if
path:match('%.[glx]z$')
or path:match('%.bz2$')
or path:match('%.lzma$')
or path:match('%.Z$')
then
tail = fn.fnamemodify(tail, ':r') tail = fn.fnamemodify(tail, ':r')
end end
local name, sect = tail:match('^(.+)%.([^.]+)$') return tail:match('^(.+)%.([^.]+)$')
return sect, name
end end
--- @return boolean --- @return boolean
@ -433,6 +397,10 @@ local function set_options()
vim.bo.filetype = 'man' vim.bo.filetype = 'man'
end end
--- Always use -l if possible. #6683
--- @type boolean?
local localfile_arg
--- @param path string --- @param path string
--- @param silent boolean? --- @param silent boolean?
--- @return string --- @return string
@ -444,11 +412,17 @@ local function get_page(path, silent)
if (vim.g.man_hardwrap or 1) ~= 1 then if (vim.g.man_hardwrap or 1) ~= 1 then
manwidth = 999 manwidth = 999
elseif vim.env.MANWIDTH then elseif vim.env.MANWIDTH then
manwidth = vim.env.MANWIDTH manwidth = vim.env.MANWIDTH --- @type string|integer
else else
manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin
end end
if localfile_arg == nil then
local mpath = get_path('man')
-- Check for -l support.
localfile_arg = (mpath and system({ 'man', '-l', mpath }, true) or '') ~= ''
end
local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path } local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path }
-- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
@ -461,47 +435,17 @@ local function get_page(path, silent)
}) })
end end
---@param lnum integer --- @param path string
---@return string --- @param psect string
local function getline(lnum)
---@diagnostic disable-next-line
return fn.getline(lnum)
end
---@param page string
local function put_page(page)
vim.bo.modifiable = true
vim.bo.readonly = false
vim.bo.swapfile = false
api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n'))
while getline(1):match('^%s*$') do
api.nvim_buf_set_lines(0, 0, 1, false, {})
end
-- XXX: nroff justifies text by filling it with whitespace. That interacts
-- badly with our use of $MANWIDTH=999. Hack around this by using a fixed
-- size for those whitespace regions.
-- Use try/catch to avoid setting v:errmsg.
vim.cmd([[
try
keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
catch
endtry
]])
vim.cmd('1') -- Move cursor to first line
highlight_man_page()
set_options()
end
local function format_candidate(path, psect) local function format_candidate(path, psect)
if matchstr(path, [[\.\%(pdf\|in\)$]]) then -- invalid extensions if vim.endswith(path, '.pdf') or vim.endswith(path, '.in') then
-- invalid extensions
return '' return ''
end end
local sect, name = extract_sect_and_name_path(path) local name, sect = parse_path(path)
if sect == psect then if sect == psect then
return name return name
elseif sect and name and matchstr(sect, psect .. '.\\+$') then -- invalid extensions elseif sect:match(psect .. '.+$') then -- invalid extensions
-- We include the section if the user provided section is a prefix -- We include the section if the user provided section is a prefix
-- of the actual section. -- of the actual section.
return ('%s(%s)'):format(name, sect) return ('%s(%s)'):format(name, sect)
@ -509,63 +453,54 @@ local function format_candidate(path, psect)
return '' return ''
end end
---@generic T
---@param list T[]
---@param elem T
---@return T[]
local function move_elem_to_head(list, elem)
---@diagnostic disable-next-line:no-unknown
local list1 = vim.tbl_filter(function(v)
return v ~= elem
end, list)
return { elem, unpack(list1) }
end
---@param sect string
--- @param name string --- @param name string
---@return string[] --- @param sect? string
local function get_paths(sect, name) --- @return string[] paths
--- @return string? err
local function get_paths(name, sect)
-- Try several sources for getting the list man directories: -- Try several sources for getting the list man directories:
-- 1. `man -w` (works on most systems) -- 1. `manpath -q`
-- 2. `manpath` -- 2. `man -w` (works on most systems)
-- 3. $MANPATH -- 3. $MANPATH
local mandirs_raw = vim.F.npcall(system, { 'man', FIND_ARG }) --
or vim.F.npcall(system, { 'manpath', '-q' }) -- Note we prefer `manpath -q` because `man -w`:
-- - does not work on MacOS 14 and later.
-- - only returns '/usr/bin/man' on MacOS 13 and earlier.
--- @type string?
local mandirs_raw = vim.F.npcall(system, { 'manpath', '-q' })
or vim.F.npcall(system, { 'man', '-w' })
or vim.env.MANPATH or vim.env.MANPATH
if not mandirs_raw then if not mandirs_raw then
man_error("Could not determine man directories from: 'man -w', 'manpath' or $MANPATH") return {}, "Could not determine man directories from: 'man -w', 'manpath' or $MANPATH"
end end
local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',') local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',')
sect = sect or ''
--- @type string[] --- @type string[]
local paths = fn.globpath(mandirs, 'man[^\\/]*/' .. name .. '*.' .. sect .. '*', false, true) local paths = fn.globpath(mandirs, 'man[^\\/]*/' .. name .. '*.' .. sect .. '*', false, true)
-- Prioritize the result from find_path as it obeys b:man_default_sects. -- Prioritize the result from find_path as it obeys b:man_default_sects.
local first = M.find_path(sect, name) local first = M._find_path(name, sect)
if first then if first then
paths = move_elem_to_head(paths, first) --- @param v string
paths = vim.tbl_filter(function(v)
return v ~= first
end, paths)
table.insert(paths, 1, first)
end end
return paths return paths
end end
---@param sect string
---@param psect string
---@param name string
---@return string[]
local function complete(sect, psect, name)
local pages = get_paths(sect, name)
-- We remove duplicates in case the same manpage in different languages was found.
return fn.uniq(fn.sort(vim.tbl_map(function(v)
return format_candidate(v, psect)
end, pages) or {}, 'i'))
end
-- see extract_sect_and_name_ref on why tolower(sect)
--- @param arg_lead string --- @param arg_lead string
--- @param cmd_line string --- @param cmd_line string
function M.man_complete(arg_lead, cmd_line, _) --- @return string? sect
--- @return string? psect
--- @return string? name
local function parse_cmdline(arg_lead, cmd_line)
local args = vim.split(cmd_line, '%s+', { trimempty = true }) local args = vim.split(cmd_line, '%s+', { trimempty = true })
local cmd_offset = fn.index(args, 'Man') local cmd_offset = fn.index(args, 'Man')
if cmd_offset > 0 then if cmd_offset > 0 then
@ -575,13 +510,13 @@ function M.man_complete(arg_lead, cmd_line, _)
end end
if #args > 3 then if #args > 3 then
return {} return
end end
if #args == 1 then if #args == 1 then
-- returning full completion is laggy. Require some arg_lead to complete -- returning full completion is laggy. Require some arg_lead to complete
-- return complete('', '', '') -- return '', '', ''
return {} return
end end
if arg_lead:match('^[^()]+%([^()]*$') then if arg_lead:match('^[^()]+%([^()]*$') then
@ -590,14 +525,15 @@ function M.man_complete(arg_lead, cmd_line, _)
-- It will offer 'priclass.d(1m)' even though section is specified as 1. -- It will offer 'priclass.d(1m)' even though section is specified as 1.
local tmp = vim.split(arg_lead, '(', { plain = true }) local tmp = vim.split(arg_lead, '(', { plain = true })
local name = tmp[1] local name = tmp[1]
-- See extract_sect_and_name_ref on why :lower()
local sect = (tmp[2] or ''):lower() local sect = (tmp[2] or ''):lower()
return complete(sect, '', name) return sect, '', name
end end
if not args[2]:match('^[^()]+$') then if not args[2]:match('^[^()]+$') then
-- cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|' -- cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|'
-- or ':Man 3() pri |' -- or ':Man 3() pri |'
return {} return
end end
if #args == 2 then if #args == 2 then
@ -617,52 +553,77 @@ function M.man_complete(arg_lead, cmd_line, _)
name = arg_lead name = arg_lead
sect = '' sect = ''
end end
return complete(sect, sect, name) return sect, sect, name
end end
if not arg_lead:match('[^()]+$') then if not arg_lead:match('[^()]+$') then
-- cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|' -- cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|'
return {} return
end end
-- cursor (|) is at ':Man 3 pri|' -- cursor (|) is at ':Man 3 pri|'
local name = arg_lead local name, sect = arg_lead, args[2]:lower()
local sect = args[2]:lower() return sect, sect, name
return complete(sect, sect, name) end
--- @param arg_lead string
--- @param cmd_line string
function M.man_complete(arg_lead, cmd_line)
local sect, psect, name = parse_cmdline(arg_lead, cmd_line)
if not (sect and psect and name) then
return {}
end
local pages = get_paths(name, sect)
-- We check for duplicates in case the same manpage in different languages
-- was found.
local pages_fmt = {} --- @type string[]
local pages_fmt_keys = {} --- @type table<string,true>
for _, v in ipairs(pages) do
local x = format_candidate(v, psect)
local xl = x:lower() -- ignore case when searching avoiding duplicates
if not pages_fmt_keys[xl] then
pages_fmt[#pages_fmt + 1] = x
pages_fmt_keys[xl] = true
end
end
table.sort(pages_fmt)
return pages_fmt
end end
--- @param pattern string --- @param pattern string
--- @return {name:string,filename:string,cmd:string}[] --- @return {name:string,filename:string,cmd:string}[]
function M.goto_tag(pattern, _, _) function M.goto_tag(pattern, _, _)
local sect, name = extract_sect_and_name_ref(pattern) local name, sect, err = parse_ref(pattern)
if err then
error(err)
end
local paths = get_paths(sect, name) local paths, err2 = get_paths(assert(name), sect)
---@type {name:string,title:string}[] if err2 then
local structured = {} error(err2)
end
--- @type table[]
local ret = {}
for _, path in ipairs(paths) do for _, path in ipairs(paths) do
sect, name = extract_sect_and_name_path(path) local pname, psect = parse_path(path)
if sect and name then ret[#ret + 1] = {
structured[#structured + 1] = { name = pname,
name = name, filename = ('man://%s(%s)'):format(pname, psect),
title = name .. '(' .. sect .. ')',
}
end
end
---@param entry {name:string,title:string}
return vim.tbl_map(function(entry)
return {
name = entry.name,
filename = 'man://' .. entry.title,
cmd = '1', cmd = '1',
} }
end, structured)
end end
-- Called when Nvim is invoked as $MANPAGER. return ret
end
--- Called when Nvim is invoked as $MANPAGER.
function M.init_pager() function M.init_pager()
if getline(1):match('^%s*$') then if fn.getline(1):match('^%s*$') then
api.nvim_buf_set_lines(0, 0, 1, false, {}) api.nvim_buf_set_lines(0, 0, 1, false, {})
else else
vim.cmd('keepjumps 1') vim.cmd('keepjumps 1')
@ -670,9 +631,10 @@ function M.init_pager()
highlight_man_page() highlight_man_page()
-- Guess the ref from the heading (which is usually uppercase, so we cannot -- Guess the ref from the heading (which is usually uppercase, so we cannot
-- know the correct casing, cf. `man glDrawArraysInstanced`). -- know the correct casing, cf. `man glDrawArraysInstanced`).
local ref = fn.substitute(matchstr(getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g') --- @type string
local ok, res = pcall(extract_sect_and_name_ref, ref) local ref = (fn.getline(1):match('^[^)]+%)') or ''):gsub(' ', '_')
vim.b.man_sect = ok and res or '' local _, sect, err = pcall(parse_ref, ref)
vim.b.man_sect = err ~= nil and sect or ''
if not fn.bufname('%'):match('man://') then -- Avoid duplicate buffers, E95. if not fn.bufname('%'):match('man://') then -- Avoid duplicate buffers, E95.
vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } }) vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } })
@ -681,21 +643,14 @@ function M.init_pager()
set_options() set_options()
end end
---@param count integer --- Combine the name and sect into a manpage reference so that all
--- verification/extraction can be kept in a single function.
--- @param args string[] --- @param args string[]
function M.open_page(count, smods, args) --- @return string? ref
local ref ---@type string local function ref_from_args(args)
if #args == 0 then if #args <= 1 then
ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>') return args[1]
if ref == '' then elseif args[1]:match('^%d$') or args[1]:match('^%d%a') or args[1]:match('^%a$') then
man_error('no identifier under cursor')
end
elseif #args == 1 then
ref = args[1]
else
-- Combine the name and sect into a manpage reference so that all
-- verification/extraction can be kept in a single function.
if args[1]:match('^%d$') or args[1]:match('^%d%a') or args[1]:match('^%a$') then
-- NB: Valid sections are not only digits, but also: -- NB: Valid sections are not only digits, but also:
-- - <digit><word> (see POSIX mans), -- - <digit><word> (see POSIX mans),
-- - and even <letter> and <word> (see, for example, by tcl/tk) -- - and even <letter> and <word> (see, for example, by tcl/tk)
@ -704,27 +659,48 @@ function M.open_page(count, smods, args)
local sect = args[1] local sect = args[1]
table.remove(args, 1) table.remove(args, 1)
local name = table.concat(args, ' ') local name = table.concat(args, ' ')
ref = ('%s(%s)'):format(name, sect) return ('%s(%s)'):format(name, sect)
else end
ref = table.concat(args, ' ')
return table.concat(args, ' ')
end
--- @param count integer
--- @param args string[]
--- @return string? err
function M.open_page(count, smods, args)
local ref = ref_from_args(args)
if not ref then
ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>')
if ref == '' then
return 'no identifier under cursor'
end end
end end
local sect, name = extract_sect_and_name_ref(ref) local name, sect, err = parse_ref(ref)
if err then
return err
end
assert(name)
if count >= 0 then if count >= 0 then
sect = tostring(count) sect = tostring(count)
end end
-- Try both spaces and underscores, use the first that exists. -- Try both spaces and underscores, use the first that exists.
local path = M.find_path(sect, name) local path = M._find_path(name, sect)
if path == nil then if not path then
path = M.find_path(sect, spaces_to_underscores(name)) --- Replace spaces in a man page name with underscores
if path == nil then --- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)';
man_error('no manual entry for ' .. name) --- while editing SQL source code, it's nice to visually select 'CREATE TABLE'
--- and hit 'K', which requires this transformation
path = M._find_path(name:gsub('%s', '_'), sect)
if not path then
return 'no manual entry for ' .. name
end end
end end
sect, name = extract_sect_and_name_path(path) name, sect = parse_path(path)
local buf = api.nvim_get_current_buf() local buf = api.nvim_get_current_buf()
local save_tfu = vim.bo[buf].tagfunc local save_tfu = vim.bo[buf].tagfunc
vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag" vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag"
@ -747,24 +723,51 @@ function M.open_page(count, smods, args)
if not ok then if not ok then
error(ret) error(ret)
else end
set_options() set_options()
end
vim.b.man_sect = sect vim.b.man_sect = sect
end end
-- Called when a man:// buffer is opened. --- Called when a man:// buffer is opened.
--- @return string? err
function M.read_page(ref) function M.read_page(ref)
local sect, name = extract_sect_and_name_ref(ref) local name, sect, err = parse_ref(ref)
local path = M.find_path(sect, name) if err then
if path == nil then return err
man_error('no manual entry for ' .. name)
end end
sect = extract_sect_and_name_path(path)
local path = M._find_path(name, sect)
if not path then
return 'no manual entry for ' .. name
end
local _, sect1 = parse_path(path)
local page = get_page(path) local page = get_page(path)
vim.b.man_sect = sect
put_page(page) vim.b.man_sect = sect1
vim.bo.modifiable = true
vim.bo.readonly = false
vim.bo.swapfile = false
api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n'))
while fn.getline(1):match('^%s*$') do
api.nvim_buf_set_lines(0, 0, 1, false, {})
end
-- XXX: nroff justifies text by filling it with whitespace. That interacts
-- badly with our use of $MANWIDTH=999. Hack around this by using a fixed
-- size for those whitespace regions.
-- Use try/catch to avoid setting v:errmsg.
vim.cmd([[
try
keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
catch
endtry
]])
vim.cmd('1') -- Move cursor to first line
highlight_man_page()
set_options()
end end
function M.show_toc() function M.show_toc()
@ -781,24 +784,13 @@ function M.show_toc()
local lnum = 2 local lnum = 2
local last_line = fn.line('$') - 1 local last_line = fn.line('$') - 1
local section_title_re = vim.regex([[^\%( \{3\}\)\=\S.*$]])
local flag_title_re = vim.regex([[^\s\+\%(+\|-\)\S\+]])
while lnum and lnum < last_line do while lnum and lnum < last_line do
local text = getline(lnum) local text = fn.getline(lnum)
if section_title_re:match_str(text) then if text:match('^%s+[-+]%S') or text:match('^ %S') or text:match('^%S') then
-- if text is a section title
toc[#toc + 1] = { toc[#toc + 1] = {
bufnr = bufnr, bufnr = bufnr,
lnum = lnum, lnum = lnum,
text = text, text = text:gsub('^%s+', ''):gsub('%s+$', ''),
}
elseif flag_title_re:match_str(text) then
-- if text is a flag title. we strip whitespaces and prepend two
-- spaces to have a consistent format in the loclist.
toc[#toc + 1] = {
bufnr = bufnr,
lnum = lnum,
text = ' ' .. fn.substitute(text, [[^\s*\(.\{-}\)\s*$]], [[\1]], ''),
} }
end end
lnum = fn.nextnonblank(lnum + 1) lnum = fn.nextnonblank(lnum + 1)
@ -810,19 +802,4 @@ function M.show_toc()
vim.w.qf_toc = bufname vim.w.qf_toc = bufname
end end
local function init()
local path = get_path('', 'man', true)
local page ---@type string?
if path ~= nil then
-- Check for -l support.
page = get_page(path, true)
end
if page == '' or page == nil then
localfile_arg = false
end
end
init()
return M return M

View File

@ -1087,8 +1087,8 @@ vim.go.cia = vim.go.completeitemalign
--- "menu" or "menuone". No effect if "longest" is present. --- "menu" or "menuone". No effect if "longest" is present.
--- ---
--- noselect Same as "noinsert", except that no menu item is --- noselect Same as "noinsert", except that no menu item is
--- pre-selected. If both "noinsert" and "noselect" are present, --- pre-selected. If both "noinsert" and "noselect" are
--- "noselect" has precedence. --- present, "noselect" has precedence.
--- ---
--- fuzzy Enable `fuzzy-matching` for completion candidates. This --- fuzzy Enable `fuzzy-matching` for completion candidates. This
--- allows for more flexible and intuitive matching, where --- allows for more flexible and intuitive matching, where

View File

@ -211,7 +211,7 @@ local function reuse_client_default(client, config)
for _, config_folder in ipairs(config_folders) do for _, config_folder in ipairs(config_folders) do
local found = false local found = false
for _, client_folder in ipairs(client.workspace_folders) do for _, client_folder in ipairs(client.workspace_folders or {}) do
if config_folder.uri == client_folder.uri then if config_folder.uri == client_folder.uri then
found = true found = true
break break
@ -1379,7 +1379,7 @@ end
--- |LspAttach| autocommand. Example: --- |LspAttach| autocommand. Example:
--- ---
--- ```lua --- ```lua
--- vim.api.nvim_create_autocommand('LspAttach', { --- vim.api.nvim_create_autocmd('LspAttach', {
--- callback = function(args) --- callback = function(args)
--- local client = vim.lsp.get_client_by_id(args.data.client_id) --- local client = vim.lsp.get_client_by_id(args.data.client_id)
--- if client:supports_method('textDocument/foldingRange') then --- if client:supports_method('textDocument/foldingRange') then

View File

@ -174,6 +174,10 @@ local validate = vim.validate
--- capabilities. --- capabilities.
--- @field server_capabilities lsp.ServerCapabilities? --- @field server_capabilities lsp.ServerCapabilities?
--- ---
--- Response from the server sent on `initialize` describing information about
--- the server.
--- @field server_info lsp.ServerInfo?
---
--- A ring buffer (|vim.ringbuf()|) containing progress messages --- A ring buffer (|vim.ringbuf()|) containing progress messages
--- sent by the server. --- sent by the server.
--- @field progress vim.lsp.Client.Progress --- @field progress vim.lsp.Client.Progress
@ -556,6 +560,8 @@ function Client:initialize()
self.offset_encoding = self.server_capabilities.positionEncoding self.offset_encoding = self.server_capabilities.positionEncoding
end end
self.server_info = result.serverInfo
if next(self.settings) then if next(self.settings) then
self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
end end

View File

@ -208,7 +208,7 @@ end
--- @param uri string --- @param uri string
--- @param client_id? integer --- @param client_id? integer
--- @param diagnostics vim.Diagnostic[] --- @param diagnostics lsp.Diagnostic[]
--- @param is_pull boolean --- @param is_pull boolean
local function handle_diagnostics(uri, client_id, diagnostics, is_pull) local function handle_diagnostics(uri, client_id, diagnostics, is_pull)
local fname = vim.uri_to_fname(uri) local fname = vim.uri_to_fname(uri)

View File

@ -40,6 +40,8 @@ local function check_active_clients()
local clients = vim.lsp.get_clients() local clients = vim.lsp.get_clients()
if next(clients) then if next(clients) then
for _, client in pairs(clients) do for _, client in pairs(clients) do
local server_version = vim.tbl_get(client, 'server_info', 'version')
or '? (no serverInfo.version response)'
local cmd ---@type string local cmd ---@type string
local ccmd = client.config.cmd local ccmd = client.config.cmd
if type(ccmd) == 'table' then if type(ccmd) == 'table' then
@ -62,6 +64,7 @@ local function check_active_clients()
end end
report_info(table.concat({ report_info(table.concat({
string.format('%s (id: %d)', client.name, client.id), string.format('%s (id: %d)', client.name, client.id),
string.format('- Version: %s', server_version),
dirs_info, dirs_info,
string.format('- Command: %s', cmd), string.format('- Command: %s', cmd),
string.format('- Settings: %s', vim.inspect(client.settings, { newline = '\n ' })), string.format('- Settings: %s', vim.inspect(client.settings, { newline = '\n ' })),

View File

@ -8,9 +8,9 @@ vim.api.nvim_create_user_command('Man', function(params)
if params.bang then if params.bang then
man.init_pager() man.init_pager()
else else
local ok, err = pcall(man.open_page, params.count, params.smods, params.fargs) local err = man.open_page(params.count, params.smods, params.fargs)
if not ok then if err then
vim.notify(man.errormsg or err, vim.log.levels.ERROR) vim.notify('man.lua: ' .. err, vim.log.levels.ERROR)
end end
end end
end, { end, {
@ -31,6 +31,9 @@ vim.api.nvim_create_autocmd('BufReadCmd', {
pattern = 'man://*', pattern = 'man://*',
nested = true, nested = true,
callback = function(params) callback = function(params)
require('man').read_page(vim.fn.matchstr(params.match, 'man://\\zs.*')) local err = require('man').read_page(assert(params.match:match('man://(.*)')))
if err then
vim.notify('man.lua: ' .. err, vim.log.levels.ERROR)
end
end, end,
}) })

View File

@ -229,10 +229,9 @@ static Object _call_function(String fn, Array args, dict_T *self, Arena *arena,
funcexe.fe_selfdict = self; funcexe.fe_selfdict = self;
TRY_WRAP(err, { TRY_WRAP(err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list` // call_func() retval is deceptive, ignore it. Instead TRY_WRAP sets `msg_list` to capture
// (see above) to capture abort-causing non-exception errors. // abort-causing non-exception errors.
call_func(fn.data, (int)fn.size, &rettv, (int)args.size, (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, vim_args, &funcexe);
vim_args, &funcexe);
}); });
if (!ERROR_SET(err)) { if (!ERROR_SET(err)) {
@ -280,18 +279,23 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
typval_T rettv; typval_T rettv;
bool mustfree = false; bool mustfree = false;
switch (dict.type) { switch (dict.type) {
case kObjectTypeString: case kObjectTypeString: {
int eval_ret;
TRY_WRAP(err, { TRY_WRAP(err, {
eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); eval_ret = eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE);
clear_evalarg(&EVALARG_EVALUATE, NULL); clear_evalarg(&EVALARG_EVALUATE, NULL);
}); });
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
return rv; return rv;
} }
if (eval_ret != OK) {
abort(); // Should not happen.
}
// Evaluation of the string arg created a new dict or increased the // Evaluation of the string arg created a new dict or increased the
// refcount of a dict. Not necessary for a RPC dict. // refcount of a dict. Not necessary for a RPC dict.
mustfree = true; mustfree = true;
break; break;
}
case kObjectTypeDict: case kObjectTypeDict:
object_to_vim(dict, &rettv, err); object_to_vim(dict, &rettv, err);
break; break;

View File

@ -1531,8 +1531,8 @@ return {
"menu" or "menuone". No effect if "longest" is present. "menu" or "menuone". No effect if "longest" is present.
noselect Same as "noinsert", except that no menu item is noselect Same as "noinsert", except that no menu item is
pre-selected. If both "noinsert" and "noselect" are present, pre-selected. If both "noinsert" and "noselect" are
"noselect" has precedence. present, "noselect" has precedence.
fuzzy Enable |fuzzy-matching| for completion candidates. This fuzzy Enable |fuzzy-matching| for completion candidates. This
allows for more flexible and intuitive matching, where allows for more flexible and intuitive matching, where

View File

@ -6378,7 +6378,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
fr = curfr; // put fr at window that grows fr = curfr; // put fr at window that grows
} }
// If not enough room thn move as far as we can // If not enough room then move as far as we can
offset = MIN(offset, room); offset = MIN(offset, room);
// No room at all, quit. // No room at all, quit.

View File

@ -1854,6 +1854,20 @@ describe('LSP', function()
end, end,
} }
end) end)
it('vim.lsp.start when existing client has no workspace_folders', function()
exec_lua(create_server_definition)
eq(
{ 2, 'foo', 'foo' },
exec_lua(function()
local server = _G._create_server()
vim.lsp.start { cmd = server.cmd, name = 'foo' }
vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' }
local foos = vim.lsp.get_clients()
return { #foos, foos[1].name, foos[2].name }
end)
)
end)
end) end)
describe('parsing tests', function() describe('parsing tests', function()

View File

@ -21,13 +21,12 @@ local function get_search_history(name)
local man = require('man') local man = require('man')
local res = {} local res = {}
--- @diagnostic disable-next-line:duplicate-set-field --- @diagnostic disable-next-line:duplicate-set-field
man.find_path = function(sect, name0) man._find_path = function(name0, sect)
table.insert(res, { sect, name0 }) table.insert(res, { sect, name0 })
return nil return nil
end end
local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) local err = man.open_page(-1, { tab = 0 }, args)
assert(not ok) assert(err and err:match('no manual entry'))
assert(rv and rv:match('no manual entry'))
return res return res
end) end)
end end
@ -225,7 +224,7 @@ describe(':Man', function()
matches('^/.+', actual_file) matches('^/.+', actual_file)
local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' }
matches( matches(
('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format(
pesc(actual_file) pesc(actual_file)
), ),
fn.system(args, { '' }) fn.system(args, { '' })
@ -235,8 +234,8 @@ describe(':Man', function()
it('tries variants with spaces, underscores #22503', function() it('tries variants with spaces, underscores #22503', function()
eq({ eq({
{ '', 'NAME WITH SPACES' }, { vim.NIL, 'NAME WITH SPACES' },
{ '', 'NAME_WITH_SPACES' }, { vim.NIL, 'NAME_WITH_SPACES' },
}, get_search_history('NAME WITH SPACES')) }, get_search_history('NAME WITH SPACES'))
eq({ eq({
{ '3', 'some other man' }, { '3', 'some other man' },
@ -255,8 +254,8 @@ describe(':Man', function()
{ 'n', 'some_other_man' }, { 'n', 'some_other_man' },
}, get_search_history('n some other man')) }, get_search_history('n some other man'))
eq({ eq({
{ '', '123some other man' }, { vim.NIL, '123some other man' },
{ '', '123some_other_man' }, { vim.NIL, '123some_other_man' },
}, get_search_history('123some other man')) }, get_search_history('123some other man'))
eq({ eq({
{ '1', 'other_man' }, { '1', 'other_man' },
@ -265,11 +264,10 @@ describe(':Man', function()
end) end)
it('can complete', function() it('can complete', function()
t.skip(t.is_os('mac') and t.is_arch('x86_64'), 'not supported on intel mac')
eq( eq(
true, true,
exec_lua(function() exec_lua(function()
return #require('man').man_complete('f', 'Man g') > 0 return #require('man').man_complete('f', 'Man f') > 0
end) end)
) )
end) end)

View File

@ -59,7 +59,8 @@ func Test_window_preview_terminal()
CheckFeature quickfix CheckFeature quickfix
" CheckFeature terminal " CheckFeature terminal
term " ++curwin " term ++curwin
term
const buf_num = bufnr('$') const buf_num = bufnr('$')
call assert_equal(1, winnr('$')) call assert_equal(1, winnr('$'))
exe 'pbuffer' . buf_num exe 'pbuffer' . buf_num

View File

@ -58,11 +58,11 @@ describe('mbyte', function()
lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc))
local str = ffi.string(buf) local str = ffi.string(buf)
if 1 > 2 then -- for debugging if 1 > 2 then -- for debugging
local tabel = {} local tbl = {}
for i = 1, #str do for i = 1, #str do
table.insert(tabel, string.format('0x%02x', string.byte(str, i))) table.insert(tbl, string.format('0x%02x', string.byte(str, i)))
end end
print('{ ' .. table.concat(tabel, ', ') .. ' }') print('{ ' .. table.concat(tbl, ', ') .. ' }')
io.stdout:flush() io.stdout:flush()
end end
return { str, firstc[0] } return { str, firstc[0] }

View File

@ -1094,7 +1094,7 @@ describe('vterm', function()
push('\x1b[0F', vt) push('\x1b[0F', vt)
cursor(0, 0, state) cursor(0, 0, state)
-- Cursor Horizonal Absolute -- Cursor Horizontal Absolute
push('\n', vt) push('\n', vt)
cursor(1, 0, state) cursor(1, 0, state)
push('\x1b[20G', vt) push('\x1b[20G', vt)
@ -3067,7 +3067,7 @@ describe('vterm', function()
screen screen
) )
-- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" -- Outputting CJK doublewidth in 80th column should wraparound to next line and not crash"
reset(nil, screen) reset(nil, screen)
push('\x1b[80G\xEF\xBC\x90', vt) push('\x1b[80G\xEF\xBC\x90', vt)
screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)