Compare commits

...

13 Commits

Author SHA1 Message Date
Lewis Russell
db2d00185d
Merge 329a6d82cb 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
zeertzjq
38f554e9c4
vim-patch:a977883: runtime(doc): Fix style in fold.txt (#31617)
closes: vim/vim#16236

a977883ef3

Co-authored-by: h-east <h.east.727@gmail.com>
2024-12-18 09:12:04 +08:00
zeertzjq
f7c42433c5
vim-patch:9.1.0940: Wrong cursor shape with "gq" and 'indentexpr' executes :normal (#31616)
Problem:  Wrong cursor shape with "gq" and 'indentexpr' executes :normal
Solution: Update cursor and mouse shape after restoring old_State.
          (zeertzjq)

closes: vim/vim#16241

Solution: Update cursor and mouse shape after restoring old_State.

6c3027744e
2024-12-18 09:04:32 +08:00
zeertzjq
51c380238c
Merge pull request #31615 from zeertzjq/vim-9.1.0936
vim-patch:9.1.{0936,0941,0942}: ComplMatchIns highlight
2024-12-18 08:40:23 +08:00
zeertzjq
b7da54aa9e vim-patch:9.1.0942: a few typos were found
Problem:  a few typos were found
Solution: fix them (zeertzjq)

closes: vim/vim#16232

d32bf0a067
2024-12-18 08:15:31 +08:00
zeertzjq
2f7b385f2e vim-patch:9.1.0941: ComplMatchIns doesn't work after multibyte chars
Problem:  ComplMatchIns doesn't work after multibyte chars
          (after v9.1.0936)
Solution: Use (ptr - line) instead of wlv.col (zeertzjq).

closes: vim/vim#16233

f4ccada5c3
2024-12-18 08:15:31 +08:00
zeertzjq
c830901e8c vim-patch:9.1.0936: cannot highlight completed text
Problem:  cannot highlight completed text
Solution: (optionally) highlight auto-completed text using the
          ComplMatchIns highlight group (glepnir)

closes: vim/vim#16173

6a38aff218

Co-authored-by: glepnir <glephunter@gmail.com>
2024-12-18 08:15:31 +08:00
Lewis Russell
329a6d82cb feat(vim.loader): defer writing cache files 2024-11-30 17:58:05 +00:00
24 changed files with 708 additions and 431 deletions

View File

@ -60,6 +60,7 @@ hi('PmenuMatch', { link = 'Pmenu' })
hi('PmenuMatchSel', { link = 'PmenuSel' })
hi('PmenuExtra', { link = 'Pmenu' })
hi('PmenuExtraSel', { link = 'PmenuSel' })
hi('ComplMatchIns', { link = 'Normal' })
hi('Substitute', { link = 'Search' })
hi('Whitespace', { link = 'NonText' })
hi('MsgSeparator', { link = 'StatusLine' })

View File

@ -101,7 +101,8 @@ The result of foldexpr then determines the fold level as follows:
"<1", "<2", .. a fold with this level ends at this line
">1", ">2", .. a fold with this level starts at this line
The result values "=", "s" and "a" are more expensive, please see |fold-expr-slow|.
The result values "=", "s" and "a" are more expensive, please see
|fold-expr-slow|.
It is not required to mark the start (end) of a fold with ">1" ("<1"), a fold
will also start (end) when the fold level is higher (lower) than the fold
@ -141,7 +142,7 @@ end in that line.
It may happen that folds are not updated properly. You can use |zx| or |zX|
to force updating folds.
Minimizing Computational Cost *fold-expr-slow*
MINIMIZING COMPUTATIONAL COST *fold-expr-slow*
Due to its computational cost, this fold method can make Vim unresponsive,
especially when the fold level of all lines have to be initially computed.
@ -149,13 +150,15 @@ Afterwards, after each change, Vim restricts the computation of foldlevels
to those lines whose fold level was affected by it (and reuses the known
foldlevels of all the others).
The fold expression should therefore strive to minimize the number of dependent
lines needed for the computation of a given line: For example, try to avoid the
"=", "a" and "s" return values, because these will require the evaluation of the
fold levels on previous lines until an independent fold level is found.
The fold expression should therefore strive to minimize the number of
dependent lines needed for the computation of a given line: For example, try
to avoid the "=", "a" and "s" return values, because these will require the
evaluation of the fold levels on previous lines until an independent fold
level is found.
If this proves difficult, the next best thing could be to cache all fold levels
in a buffer-local variable (b:foldlevels) that is only updated on |b:changedtick|:
If this proves difficult, the next best thing could be to cache all fold
levels in a buffer-local variable (b:foldlevels) that is only updated on
|b:changedtick|:
>vim
func MyFoldFunc()
if b:lasttick == b:changedtick
@ -406,8 +409,8 @@ zX Undo manually opened and closed folds: re-apply 'foldlevel'.
Also forces recomputing folds, like |zx|.
*zm*
zm Fold more: Subtract |v:count1| from 'foldlevel'. If 'foldlevel' was
already zero nothing happens.
zm Fold more: Subtract |v:count1| from 'foldlevel'. If
'foldlevel' was already zero nothing happens.
'foldenable' will be set.
*zM*

View File

@ -1126,6 +1126,9 @@ Lua module: vim.lsp.client *lsp-client*
• {server_capabilities} (`lsp.ServerCapabilities?`) Response from the
server sent on `initialize` describing the
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
(|vim.ringbuf()|) containing progress messages
sent by the server. See

View File

@ -118,8 +118,8 @@ Note that when using ":" any motion becomes charwise exclusive.
*inclusive-motion-selection-exclusive*
When 'selection' is "exclusive", |Visual| mode is active and an inclusive
motion has been used, the cursor position will be adjusted by another
character to the right, so that visual selction includes the expected text and
can be acted upon.
character to the right, so that the Visual selection includes the expected
text and can be acted upon.
*forced-motion*
FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE

View File

@ -115,6 +115,7 @@ LSP
• |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`
parameter.
• `:checkhealth vim.lsp` displays the server version (if available).
LUA
@ -216,6 +217,7 @@ EDITOR
• On Windows, filename arguments on the command-line prefixed with "~\" or
"~/" are now expanded to the user's profile directory, not a relative path
to a literal "~" directory.
• |hl-ComplMatchIns| shows matched text of the currently inserted completion.
• |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
EVENTS

View File

@ -5243,6 +5243,8 @@ PmenuMatch Popup menu: Matched text in normal item. Combined with
*hl-PmenuMatchSel*
PmenuMatchSel Popup menu: Matched text in selected item. Combined with
|hl-PmenuMatch| and |hl-PmenuSel|.
*hl-ComplMatchIns*
ComplMatchIns Matched text of the currently inserted completion.
*hl-Question*
Question |hit-enter| prompt and yes/no questions.
*hl-QuickFixLine*

View File

@ -1,19 +1,8 @@
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.
--- Run a system command and timeout after 10 seconds.
--- @param cmd string[]
--- @param silent boolean?
--- @param env? table<string,string|number>
@ -24,7 +13,7 @@ local function system(cmd, silent, env)
if not silent then
if r.code ~= 0 then
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
assert(r.stdout ~= '')
end
@ -32,65 +21,64 @@ local function system(cmd, silent, env)
return assert(r.stdout)
end
--- @enum Man.Attribute
local Attrs = {
None = 0,
Bold = 1,
Underline = 2,
Italic = 3,
}
--- @param line string
---@param linenr integer
local function highlight_line(line, linenr)
--- @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
@ -103,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
@ -116,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
@ -158,10 +146,11 @@ local function highlight_line(line, linenr)
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
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
@ -187,55 +176,40 @@ 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
api.nvim_buf_add_highlight(0, -1, HlGroups[hl.attr], hl.row, hl.start, hl.final)
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
@ -258,12 +232,12 @@ local function get_path(sect, name, silent)
-- inconsistently supported. Instead, call -w with a section and a name.
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
@ -282,7 +256,7 @@ local function get_path(sect, name, silent)
--- @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 = {}
@ -293,73 +267,59 @@ local function get_path(sect, name, silent)
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
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
--- 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 '-'")
--- @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
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 })
-- 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.
local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower()
local name = parts[1]
return sect, name
return name, sect:lower()
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.
name = ref:match('[^()]+')
if not name then
return nil, nil, ('invalid manpage reference "%s"'):format(ref)
end
return name
end
--- 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?
---@param name string
function M.find_path(sect, name)
--- @return string? path
function M._find_path(name, sect)
if sect and sect ~= '' then
local ret = get_path(sect, name, true)
local ret = get_path(name, sect)
if ret then
return ret
end
end
if vim.b.man_default_sects ~= nil then
local sects = vim.split(vim.b.man_default_sects, ',', { plain = true, trimempty = true })
for _, sec in ipairs(sects) do
local ret = get_path(sec, name, true)
for sec in vim.gsplit(vim.b.man_default_sects, ',', { trimempty = true }) do
local ret = get_path(name, sec)
if ret then
return ret
end
@ -367,17 +327,18 @@ function M.find_path(sect, name)
end
-- if none of the above worked, we will try with no section
local res_empty_sect = get_path('', name, true)
if res_empty_sect then
return res_empty_sect
local ret = get_path(name)
if ret then
return ret
end
-- if that still didn't work, we will check for $MANSECT and try again with it
-- unset
if vim.env.MANSECT then
--- @type string
local mansect = vim.env.MANSECT
vim.env.MANSECT = nil
local res = get_path('', name, true)
local res = get_path(name)
vim.env.MANSECT = mansect
if res then
return res
@ -388,21 +349,24 @@ function M.find_path(sect, name)
return nil
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` (try `:Man 3 App::CLI`).
-- Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we
-- still want the name of the buffer to be 'printf'.
--- Extracts the name/section from the 'path/name.sect', because sometimes the
--- actual section is more specific than what we provided to `man`
--- (try `:Man 3 App::CLI`). 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
---@return string, string
local function extract_sect_and_name_path(path)
--- @return string name
--- @return string sect
local function parse_path(path)
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')
end
local name, sect = tail:match('^(.+)%.([^.]+)$')
return sect, name
return tail:match('^(.+)%.([^.]+)$')
end
--- @return boolean
@ -433,6 +397,10 @@ local function set_options()
vim.bo.filetype = 'man'
end
--- Always use -l if possible. #6683
--- @type boolean?
local localfile_arg
--- @param path string
--- @param silent boolean?
--- @return string
@ -444,11 +412,17 @@ local function get_page(path, silent)
if (vim.g.man_hardwrap or 1) ~= 1 then
manwidth = 999
elseif vim.env.MANWIDTH then
manwidth = vim.env.MANWIDTH
manwidth = vim.env.MANWIDTH --- @type string|integer
else
manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin
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 }
-- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
@ -461,47 +435,17 @@ local function get_page(path, silent)
})
end
---@param lnum integer
---@return 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
--- @param path string
--- @param psect string
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 ''
end
local sect, name = extract_sect_and_name_path(path)
local name, sect = parse_path(path)
if sect == psect then
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
-- of the actual section.
return ('%s(%s)'):format(name, sect)
@ -509,63 +453,54 @@ local function format_candidate(path, psect)
return ''
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
---@return string[]
local function get_paths(sect, name)
--- @param sect? string
--- @return string[] paths
--- @return string? err
local function get_paths(name, sect)
-- Try several sources for getting the list man directories:
-- 1. `man -w` (works on most systems)
-- 2. `manpath`
-- 1. `manpath -q`
-- 2. `man -w` (works on most systems)
-- 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
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
local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',')
sect = sect or ''
--- @type string[]
local paths = fn.globpath(mandirs, 'man[^\\/]*/' .. name .. '*.' .. sect .. '*', false, true)
-- 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
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
return paths
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 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 cmd_offset = fn.index(args, 'Man')
if cmd_offset > 0 then
@ -575,13 +510,13 @@ function M.man_complete(arg_lead, cmd_line, _)
end
if #args > 3 then
return {}
return
end
if #args == 1 then
-- returning full completion is laggy. Require some arg_lead to complete
-- return complete('', '', '')
return {}
-- return '', '', ''
return
end
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.
local tmp = vim.split(arg_lead, '(', { plain = true })
local name = tmp[1]
-- See extract_sect_and_name_ref on why :lower()
local sect = (tmp[2] or ''):lower()
return complete(sect, '', name)
return sect, '', name
end
if not args[2]:match('^[^()]+$') then
-- cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|'
-- or ':Man 3() pri |'
return {}
return
end
if #args == 2 then
@ -617,52 +553,77 @@ function M.man_complete(arg_lead, cmd_line, _)
name = arg_lead
sect = ''
end
return complete(sect, sect, name)
return sect, sect, name
end
if not arg_lead:match('[^()]+$') then
-- cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|'
return {}
return
end
-- cursor (|) is at ':Man 3 pri|'
local name = arg_lead
local sect = args[2]:lower()
return complete(sect, sect, name)
local name, sect = arg_lead, args[2]:lower()
return 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
--- @param pattern string
--- @return {name:string,filename:string,cmd:string}[]
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)
---@type {name:string,title:string}[]
local structured = {}
local paths, err2 = get_paths(assert(name), sect)
if err2 then
error(err2)
end
--- @type table[]
local ret = {}
for _, path in ipairs(paths) do
sect, name = extract_sect_and_name_path(path)
if sect and name then
structured[#structured + 1] = {
name = name,
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,
local pname, psect = parse_path(path)
ret[#ret + 1] = {
name = pname,
filename = ('man://%s(%s)'):format(pname, psect),
cmd = '1',
}
end, structured)
end
-- Called when Nvim is invoked as $MANPAGER.
return ret
end
--- Called when Nvim is invoked as $MANPAGER.
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, {})
else
vim.cmd('keepjumps 1')
@ -670,9 +631,10 @@ function M.init_pager()
highlight_man_page()
-- Guess the ref from the heading (which is usually uppercase, so we cannot
-- know the correct casing, cf. `man glDrawArraysInstanced`).
local ref = fn.substitute(matchstr(getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g')
local ok, res = pcall(extract_sect_and_name_ref, ref)
vim.b.man_sect = ok and res or ''
--- @type string
local ref = (fn.getline(1):match('^[^)]+%)') or ''):gsub(' ', '_')
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.
vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } })
@ -681,21 +643,14 @@ function M.init_pager()
set_options()
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[]
function M.open_page(count, smods, args)
local ref ---@type string
if #args == 0 then
ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>')
if ref == '' 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
--- @return string? ref
local function ref_from_args(args)
if #args <= 1 then
return args[1]
elseif 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:
-- - <digit><word> (see POSIX mans),
-- - 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]
table.remove(args, 1)
local name = table.concat(args, ' ')
ref = ('%s(%s)'):format(name, sect)
else
ref = table.concat(args, ' ')
return ('%s(%s)'):format(name, sect)
end
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
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
sect = tostring(count)
end
-- Try both spaces and underscores, use the first that exists.
local path = M.find_path(sect, name)
if path == nil then
path = M.find_path(sect, spaces_to_underscores(name))
if path == nil then
man_error('no manual entry for ' .. name)
local path = M._find_path(name, sect)
if not path then
--- 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
path = M._find_path(name:gsub('%s', '_'), sect)
if not path then
return 'no manual entry for ' .. name
end
end
sect, name = extract_sect_and_name_path(path)
name, sect = parse_path(path)
local buf = api.nvim_get_current_buf()
local save_tfu = vim.bo[buf].tagfunc
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
error(ret)
else
end
set_options()
end
vim.b.man_sect = sect
end
-- Called when a man:// buffer is opened.
--- Called when a man:// buffer is opened.
--- @return string? err
function M.read_page(ref)
local sect, name = extract_sect_and_name_ref(ref)
local path = M.find_path(sect, name)
if path == nil then
man_error('no manual entry for ' .. name)
local name, sect, err = parse_ref(ref)
if err then
return err
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)
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
function M.show_toc()
@ -781,24 +784,13 @@ function M.show_toc()
local lnum = 2
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
local text = getline(lnum)
if section_title_re:match_str(text) then
-- if text is a section title
local text = fn.getline(lnum)
if text:match('^%s+[-+]%S') or text:match('^ %S') or text:match('^%S') then
toc[#toc + 1] = {
bufnr = bufnr,
lnum = lnum,
text = text,
}
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]], ''),
text = text:gsub('^%s+', ''):gsub('%s+$', ''),
}
end
lnum = fn.nextnonblank(lnum + 1)
@ -810,19 +802,4 @@ function M.show_toc()
vim.w.qf_toc = bufname
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

View File

@ -120,12 +120,22 @@ local function cache_filename(name)
return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac')
end
local writing = {} --- @type table<string,function>
--- Saves the cache entry for a given module or file
--- @param cname string cache filename
--- @param hash vim.loader.CacheHash
--- @param chunk function
local function write_cachefile(cname, hash, chunk)
local f = assert(uv.fs_open(cname, 'w', 438))
if writing[cname] then
return
end
writing[cname] = chunk
uv.fs_open(cname, 'w', 438, function(err, f)
if err then
error(err)
end
local header = {
VERSION,
hash.size,
@ -135,6 +145,8 @@ local function write_cachefile(cname, hash, chunk)
uv.fs_write(f, table.concat(header, ',') .. '\0')
uv.fs_write(f, string.dump(chunk))
uv.fs_close(f)
writing[cname] = nil
end)
end
--- @param path string
@ -255,6 +267,10 @@ local function loadfile_cached(filename, mode, env)
end
end
if writing[cname] then
return writing[cname]
end
local chunk, err = _loadfile(modpath, mode, env)
if chunk and stat then
write_cachefile(cname, stat, chunk)

View File

@ -211,7 +211,7 @@ local function reuse_client_default(client, config)
for _, config_folder in ipairs(config_folders) do
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
found = true
break

View File

@ -174,6 +174,10 @@ local validate = vim.validate
--- capabilities.
--- @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
--- sent by the server.
--- @field progress vim.lsp.Client.Progress
@ -556,6 +560,8 @@ function Client:initialize()
self.offset_encoding = self.server_capabilities.positionEncoding
end
self.server_info = result.serverInfo
if next(self.settings) then
self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
end

View File

@ -40,6 +40,8 @@ local function check_active_clients()
local clients = vim.lsp.get_clients()
if next(clients) then
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 ccmd = client.config.cmd
if type(ccmd) == 'table' then
@ -62,6 +64,7 @@ local function check_active_clients()
end
report_info(table.concat({
string.format('%s (id: %d)', client.name, client.id),
string.format('- Version: %s', server_version),
dirs_info,
string.format('- Command: %s', cmd),
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
man.init_pager()
else
local ok, err = pcall(man.open_page, params.count, params.smods, params.fargs)
if not ok then
vim.notify(man.errormsg or err, vim.log.levels.ERROR)
local err = man.open_page(params.count, params.smods, params.fargs)
if err then
vim.notify('man.lua: ' .. err, vim.log.levels.ERROR)
end
end
end, {
@ -31,6 +31,9 @@ vim.api.nvim_create_autocmd('BufReadCmd', {
pattern = 'man://*',
nested = true,
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,
})

View File

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

View File

@ -31,6 +31,7 @@
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/insexpand.h"
#include "nvim/mark_defs.h"
#include "nvim/marktree_defs.h"
#include "nvim/match.h"
@ -934,6 +935,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
colnr_T vcol_prev = -1; // "wlv.vcol" of previous character
ScreenGrid *grid = &wp->w_grid; // grid specific to the window
const bool in_curline = wp == curwin && lnum == curwin->w_cursor.lnum;
const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
const bool has_foldtext = has_fold && *wp->w_p_fdt != NUL;
@ -1117,7 +1119,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
// Check if the char under the cursor should be inverted (highlighted).
if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin
if (!highlight_match && in_curline
&& cursor_is_block_during_visual(*p_sel == 'e')) {
noinvcur = true;
}
@ -1629,8 +1631,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
// When still displaying '$' of change command, stop at cursor.
if (dollar_vcol >= 0 && wp == curwin
&& lnum == wp->w_cursor.lnum && wlv.vcol >= wp->w_virtcol) {
if (dollar_vcol >= 0 && in_curline && wlv.vcol >= wp->w_virtcol) {
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
// don't clear anything after wlv.col
wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0);
@ -1786,6 +1787,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
}
// Apply ComplMatchIns highlight if needed.
if (wlv.filler_todo <= 0
&& (State & MODE_INSERT) && in_curline && ins_compl_active()) {
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
if (ins_match_attr > 0) {
char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr);
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
}
}
if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
const int v = (int)(ptr - line);
linenr_T lnume = lnum + foldinfo.fi_lines - 1;
@ -2446,8 +2457,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// With 'virtualedit' we may never reach cursor position, but we still
// need to correct the cursor column, so do that at end of line.
if (!did_wcol && wlv.filler_todo <= 0
&& wp == curwin && lnum == wp->w_cursor.lnum
&& conceal_cursor_line(wp)
&& in_curline && conceal_cursor_line(wp)
&& (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) {
wp->w_wcol = wlv.col - wlv.boguscols;
if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) {
@ -2640,7 +2650,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// Update w_cline_height and w_cline_folded if the cursor line was
// updated (saves a call to plines_win() later).
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
if (in_curline) {
curwin->w_cline_row = startrow;
curwin->w_cline_height = wlv.row - startrow;
curwin->w_cline_folded = has_fold;

View File

@ -173,6 +173,7 @@ static const char *highlight_init_both[] = {
"default link PmenuKind Pmenu",
"default link PmenuKindSel PmenuSel",
"default link PmenuSbar Pmenu",
"default link ComplMatchIns Normal",
"default link Substitute Search",
"default link StatusLineTerm StatusLine",
"default link StatusLineTermNC StatusLineNC",

View File

@ -256,6 +256,7 @@ static pos_T compl_startpos;
static int compl_length = 0;
static colnr_T compl_col = 0; ///< column where the text starts
///< that is being completed
static colnr_T compl_ins_end_col = 0;
static char *compl_orig_text = NULL; ///< text as it was before
///< completion started
/// Undo information to restore extmarks for original text.
@ -282,6 +283,11 @@ static size_t spell_bad_len = 0; // length of located bad word
static int compl_selected_item = -1;
// "compl_match_array" points the currently displayed list of entries in the
// popup menu. It is NULL when there is no popup menu.
static pumitem_T *compl_match_array = NULL;
static int compl_match_arraysize;
/// CTRL-X pressed in Insert mode.
void ins_ctrl_x(void)
{
@ -943,6 +949,30 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len)
return strncmp(match->cp_str, str, len) == 0;
}
/// when len is -1 mean use whole length of p otherwise part of p
static void ins_compl_insert_bytes(char *p, int len)
FUNC_ATTR_NONNULL_ALL
{
if (len == -1) {
len = (int)strlen(p);
}
assert(len >= 0);
ins_bytes_len(p, (size_t)len);
compl_ins_end_col = curwin->w_cursor.col - 1;
}
/// Checks if the column is within the currently inserted completion text
/// column range. If it is, it returns a special highlight attribute.
/// -1 mean normal item.
int ins_compl_col_range_attr(int col)
{
if (col >= compl_col && col < compl_ins_end_col) {
return syn_name2attr("ComplMatchIns");
}
return -1;
}
/// Reduce the longest common string for match "match".
static void ins_compl_longest_match(compl_T *match)
{
@ -952,7 +982,7 @@ static void ins_compl_longest_match(compl_T *match)
bool had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete(false);
ins_bytes(compl_leader + get_compl_len());
ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
ins_redraw(false);
// When the match isn't there (to avoid matching itself) remove it
@ -986,7 +1016,7 @@ static void ins_compl_longest_match(compl_T *match)
*p = NUL;
bool had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete(false);
ins_bytes(compl_leader + get_compl_len());
ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
ins_redraw(false);
// When the match isn't there (to avoid matching itself) remove it
@ -1058,11 +1088,6 @@ unsigned get_cot_flags(void)
return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags;
}
/// "compl_match_array" points the currently displayed list of entries in the
/// popup menu. It is NULL when there is no popup menu.
static pumitem_T *compl_match_array = NULL;
static int compl_match_arraysize;
/// Remove any popup menu.
static void ins_compl_del_pum(void)
{
@ -1678,6 +1703,7 @@ void ins_compl_clear(void)
compl_cont_status = 0;
compl_started = false;
compl_matches = 0;
compl_ins_end_col = 0;
XFREE_CLEAR(compl_pattern);
compl_patternlen = 0;
XFREE_CLEAR(compl_leader);
@ -1802,7 +1828,7 @@ static void ins_compl_new_leader(void)
{
ins_compl_del_pum();
ins_compl_delete(true);
ins_bytes(compl_leader + get_compl_len());
ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
compl_used_match = false;
if (compl_started) {
@ -2137,7 +2163,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
const int compl_len = get_compl_len();
const int len = (int)strlen(p);
if (len > compl_len) {
ins_bytes_len(p + compl_len, (size_t)(len - compl_len));
ins_compl_insert_bytes(p + compl_len, len - compl_len);
}
}
restore_orig_extmarks();
@ -3639,7 +3665,7 @@ void ins_compl_insert(bool in_compl_func)
// Make sure we don't go over the end of the string, this can happen with
// illegal bytes.
if (compl_len < (int)strlen(compl_shown_match->cp_str)) {
ins_bytes(compl_shown_match->cp_str + compl_len);
ins_compl_insert_bytes(compl_shown_match->cp_str + compl_len, -1);
}
compl_used_match = !match_at_original_text(compl_shown_match);
@ -3888,14 +3914,15 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
// Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
ins_bytes(compl_orig_text + get_compl_len());
ins_compl_insert_bytes(compl_orig_text + get_compl_len(), -1);
compl_used_match = false;
restore_orig_extmarks();
} else if (insert_match) {
if (!compl_get_longest || compl_used_match) {
ins_compl_insert(in_compl_func);
} else {
ins_bytes(compl_leader + get_compl_len());
assert(compl_leader != NULL);
ins_compl_insert_bytes(compl_leader + get_compl_len(), -1);
}
if (!strcmp(compl_curr_match->cp_str, compl_orig_text)) {
restore_orig_extmarks();

View File

@ -35,6 +35,7 @@
#include "nvim/strings.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
@ -1049,12 +1050,18 @@ void format_lines(linenr_T line_count, bool avoid_fex)
State = MODE_INSERT; // for open_line()
smd_save = p_smd;
p_smd = false;
insertchar(NUL, INSCHAR_FORMAT
+ (do_comments ? INSCHAR_DO_COM : 0)
+ (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0)
+ (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent);
State = old_State;
p_smd = smd_save;
// Cursor shape may have been updated (e.g. by :normal) in insertchar(),
// so it needs to be updated here.
ui_cursor_shape();
second_indent = -1;
// at end of par.: need to set indent of next par.
need_set_indent = is_end_par;

View File

@ -1854,6 +1854,20 @@ describe('LSP', function()
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)
describe('parsing tests', function()

View File

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

View File

@ -94,6 +94,46 @@ describe('ui mode_change event', function()
}
end)
-- oldtest: Test_indent_norm_with_gq()
it('is restored to Normal mode after "gq" indents using :normal #12309', function()
screen:try_resize(60, 6)
n.exec([[
func Indent()
exe "normal! \<Ignore>"
return 0
endfunc
setlocal indentexpr=Indent()
call setline(1, [repeat('a', 80), repeat('b', 80)])
]])
feed('ggVG')
screen:expect {
grid = [[
{17:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{17:aaaaaaaaaaaaaaaaaaaa} |
^b{17:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}|
{17:bbbbbbbbbbbbbbbbbbbb} |
{1:~ }|
{5:-- VISUAL LINE --} |
]],
mode = 'visual',
}
feed('gq')
screen:expect {
grid = [[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
aaaaaaaaaaaaaaaaaaaa |
^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
bbbbbbbbbbbbbbbbbbbb |
{1:~ }|
|
]],
mode = 'normal',
}
end)
it('works in insert mode', function()
feed('i')
screen:expect {

View File

@ -5561,6 +5561,71 @@ describe('builtin popupmenu', function()
]])
feed('<C-E><ESC>')
end)
-- oldtest: Test_pum_matchins_highlight()
it('with ComplMatchIns highlight', function()
exec([[
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi ComplMatchIns guifg=red
]])
feed('Sαβγ <C-X><C-O>')
screen:expect([[
αβγ {8:foo}^ |
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><Esc>')
feed('Sαβγ <C-X><C-O><C-N>')
screen:expect([[
αβγ {8:bar}^ |
{1:~ }{n: foo }{1: }|
{1:~ }{s: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 2 of 3} |
]])
feed('<C-E><Esc>')
feed('Sαβγ <C-X><C-O><C-N><C-N>')
screen:expect([[
αβγ {8:}^ |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{s: }{1: }|
{1:~ }|*15
{2:-- }{5:match 3 of 3} |
]])
feed('<C-E><Esc>')
-- restore after accept
feed('Sαβγ <C-X><C-O><C-Y>')
screen:expect([[
αβγ foo^ |
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
-- restore after cancel completion
feed('Sαβγ <C-X><C-O><Space>')
screen:expect([[
αβγ foo ^ |
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
end)
end
end

View File

@ -1,5 +1,8 @@
" Test for various indent options
source shared.vim
source check.vim
func Test_preserveindent()
new
" Test for autoindent copying indent from the previous line
@ -303,4 +306,50 @@ func Test_indent_overflow_count2()
close!
endfunc
" Test that mouse shape is restored to Normal mode after using "gq" when
" 'indentexpr' executes :normal.
func Test_indent_norm_with_gq()
CheckFeature mouseshape
CheckCanRunGui
let lines =<< trim END
func Indent()
exe "normal! \<Ignore>"
return 0
endfunc
setlocal indentexpr=Indent()
END
call writefile(lines, 'Xindentexpr.vim', 'D')
let lines =<< trim END
vim9script
var mouse_shapes = []
setline(1, [repeat('a', 80), repeat('b', 80)])
feedkeys('ggVG')
timer_start(50, (_) => {
mouse_shapes += [getmouseshape()]
timer_start(50, (_) => {
feedkeys('gq')
timer_start(50, (_) => {
mouse_shapes += [getmouseshape()]
timer_start(50, (_) => {
writefile(mouse_shapes, 'Xmouseshapes')
quit!
})
})
})
})
END
call writefile(lines, 'Xmouseshape.vim', 'D')
call RunVim([], [], "-g -S Xindentexpr.vim -S Xmouseshape.vim")
call WaitForAssert({-> assert_equal(['rightup-arrow', 'arrow'],
\ readfile('Xmouseshapes'))}, 300)
call delete('Xmouseshapes')
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -1713,4 +1713,49 @@ func Test_pum_keep_select()
call StopVimInTerminal(buf)
endfunc
func Test_pum_matchins_highlight()
CheckScreendump
let lines =<< trim END
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi ComplMatchIns ctermfg=red
END
call writefile(lines, 'Xscript', 'D')
let buf = RunVimInTerminal('-S Xscript', {})
call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_01', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-N>")
call VerifyScreenDump(buf, 'Test_pum_matchins_02', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-N>\<C-N>")
call VerifyScreenDump(buf, 'Test_pum_matchins_03', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" restore after accept
call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>")
call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" restore after cancel completion
call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>")
call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
call StopVimInTerminal(buf)
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

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