Compare commits

...

21 Commits

Author SHA1 Message Date
Gregory Anders
9644e65493
Merge 169a159ccc into 4c5bce9cb4 2024-09-10 22:45:47 +05:30
bfredl
4c5bce9cb4
Merge pull request #30295 from glepnir/nfloat
fix(highlight): floating windows inherit NormalFloat from global-ns
2024-09-10 11:11:07 +02:00
Justin M. Keyes
5d7853f229
refactor(os/input.c): rename os_inchar => input_get #30327
Problem:
The name `os_inchar` (from Vim's old `mch_inchar`) is ambiguous:
"inchar" sounds like it could be reading or enqueuing (setting) input.
Its docstring is also ambiguous.

Solution:
- Rename `os_inchar` to `input_get`.
- Write some mf'ing docstrings.
- Add assert() in TRY_READ().
2024-09-10 01:14:18 -07:00
tris203
f279d1ae33 fix(lsp): handle encoding bounds in str_utfindex_enc
Problem:
str_utfindex_enc could return an error if the index was longer than the
line length. This was handled in each of the calls to it individually

Solution:
* Fix the call at the source level so that if the index is higher than
  the line length, utf length is returned
2024-09-10 09:25:04 +02:00
LosFarmosCTL
9ddfcb64bf
fix(runtime): add remaining missing commentstrings (#30252) 2024-09-10 15:01:25 +08:00
glepnir
8e81212e15 fix(highlight): floating windows inherit NormalFloat from global-ns
Problem:
floating windows did not correctly inherit the NormalFloat highlight
group from the global namespace when it was not defined in the window-specific
namespace. This led to floating windows losing their background highlight when
switching between namespaces.

Solution:
Updated the window highlight logic in update_window_hl() to handle the fallback.

This fix resolves issues with floating window backgrounds not displaying as expected
in certain namespace configurations.
2024-09-10 13:31:42 +08:00
Justin M. Keyes
648d6426c8
fix(server): CID 509282: DEADCODE #30316
listen_addr cannot be NULL at this point.
2024-09-09 05:14:47 -07:00
Justin M. Keyes
f0334c2c71
Merge #30312 from justinmk/testslashes 2024-09-09 04:00:35 -07:00
Justin M. Keyes
c8e3618e0e fix(test): "tempdir not a directory" in CI logs
$NVIM_LOG_FILE: /Users/runner/work/neovim/neovim/build/.nvimlog
    WRN 2024-09-08T21:48:13.279 ?.21134    vim_mktempdir:3281: $TMPDIR tempdir not a directory (or does not exist): TMPDIR-should-be-ignored
    WRN 2024-09-08T21:48:13.312 ?.21137    vim_mktempdir:3281: $TMPDIR tempdir not a directory (or does not exist): TMPDIR-should-be-ignored
2024-09-09 12:23:54 +02:00
Justin M. Keyes
ed832b9ddf refactor(test): rename alter_slashes, invert its behavior
- `alter_slashes` belongs in `testutil.lua`, not `testnvim.lua`.
- `alter_slashes` is an unusual name. Rename it to `fix_slashes`.
- invert its behavior, to emphasize that `/` slashes are the preferred,
  pervasive convention, not `\` slashes.
2024-09-09 12:23:54 +02:00
Justin M. Keyes
8a2aec9974
fix(startup): server fails if $NVIM_APPNAME is relative dir #30310
Problem:
If $NVIM_APPNAME is a relative dir path, Nvim fails to start its
primary/default server, and `v:servername` is empty.
Root cause is d34c64e342, but this wasn't
noticed until 96128a5076 started reporting the error more loudly.

Solution:
- `server_address_new`: replace slashes "/" in the appname before using
  it as a servername.
- `vim_mktempdir`: always prefer the system-wide top-level "nvim.user/"
  directory. That isn't intended to be specific to NVIM_APPNAME; rather,
  each *subdirectory* ("nvim.user/xxx") is owned by each Nvim instance.
  Nvim "apps" can be identified by the server socket(s) stored in those
  per-Nvim subdirs.

fix #30256
2024-09-08 12:48:32 -07:00
Christian Clason
3a88113246 fix(lua): revert vim.tbl_extend behavior change and document it
Problem: vim.tbl_deep_extend had an undocumented feature where arrays
(integer-indexed tables) were not merged but compared literally (used
for merging default and user config, where one list should overwrite the
other completely). Turns out this behavior was relied on in quite a
number of plugins (even though it wasn't a robust solution even for that
use case, since lists of tables (e.g., plugin specs) can be array-like
as well).

Solution: Revert the removal of this special feature. Check for
list-like (contiguous integer indices) instead, as this is closer to the
intent. Document this behavior.
2024-09-08 21:06:13 +02:00
Justin M. Keyes
08153ddd1c
fix(startup): ignore broken $XDG_RUNTIME_DIR #30285
Problem:
$XDG_RUNTIME_DIR may be broken on WSL, which prevents starting (and even
building) Nvim. #30282

Solution:
- When startup fails, mention the servername in the error message.
- If an autogenerated server address fails, log an error and continue
  with an empty `v:servername`. It's only fatal if a user provides a bad
  `--listen` or `$NVIM_LISTEN_ADDRESS` address.

Before:

    $ nvim --headless --listen ./hello.sock
    nvim: Failed to --listen: "address already in use"
    $ NVIM_LISTEN_ADDRESS='./hello.sock' ./build/bin/nvim --headless
    nvim: Failed to --listen: "address already in use"

After:

    $ nvim --headless --listen ./hello.sock
    nvim: Failed to --listen: address already in use: "./hello.sock"
    $ NVIM_LISTEN_ADDRESS='./hello.sock' ./build/bin/nvim --headless
    nvim: Failed $NVIM_LISTEN_ADDRESS: address already in use: "./hello.sock"
2024-09-08 07:07:19 -07:00
Tristan Knight
003b8a251d
fix(lsp): handle out-of-bounds character positions #30288
Problem:
str_byteindex_enc could return an error if the index was longer than the
lline length. This was handled in each of the calls to it individually

Solution:
* Fix the call at the source level so that if the index is higher than
  the line length, line length is returned as per LSP specification
* Remove pcalls on str_byteindex_enc calls. No longer needed now that
  str_byteindex_enc has a bounds check.
2024-09-08 03:44:46 -07:00
Justin M. Keyes
0cfbc6eaff
Merge #30105 fix(tohtml): quote font-family names 2024-09-08 03:32:33 -07:00
Justin M. Keyes
95b65a7554 test(tohtml): simplify font test 2024-09-08 12:17:42 +02:00
yayoyuyu
e37404f7fe fix(tohtml): enclose font-family names in quotation marks
Font-family names must be enclosed in quotation marks to ensure that
fonts are applied correctly when there are spaces in the name.

Fix an issue where multiple fonts specified in `vim.o.guifont` are
inserted as a single element, treating them as a single font.

Support for escaping commas with backslash and ignoring spaces
after a comma.

ref `:help 'guifont'`
2024-09-08 12:15:50 +02:00
zeertzjq
b40ec083ae
vim-patch:b584117: runtime(doc): buffers can be re-used (#30300)
while at it, also move the note about :wincmd
directly to :h :wincmd, it doesn't seem to belong to the buffer section.

closes: vim/vim#15636

b584117b05

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-09-08 05:41:44 +08:00
Yi Ming
d338ec9cb2
fix(vim.ui.open): prefer xdg-open on WSL #30302
xdg-open is usually not installed in WSL. But if the user deliberately
installs it, presumably they want to prioritize it.
2024-09-07 14:14:37 -07:00
Justin M. Keyes
5ddf2ab768
test(lua): tbl_deep_extend "after second argument" #30297 2024-09-07 09:41:02 -07:00
Gregory Anders
169a159ccc feat(diagnostic)!: filter diagnostics by severity before passing to handlers
BREAKING CHANGE: This changes the list of diagnostics that are passed to
a diagnostic handler. If a handler is already filtering by severity
itself then this won't break anything, since the handler's filtering
will become a no-op. But handlers which depend on receiving the full
list of diagnostics may break.

Note that diagnostics are only filtered if the handler's configuration
has the `severity` option set. If `severity` is not set, the handler
still receives the full list of diagnostics.
2024-08-16 19:55:32 -05:00
47 changed files with 848 additions and 656 deletions

View File

@ -2230,6 +2230,12 @@ vim.tbl_count({t}) *vim.tbl_count()*
vim.tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()*
Merges recursively two or more tables.
Only values that are empty tables or tables that are not |lua-list|s
(indexed by consecutive integers starting from 1) are merged recursively.
This is useful for merging nested tables like default and user
configurations where lists should be treated as literals (i.e., are
overwritten instead of merged).
Parameters: ~
• {behavior} (`'error'|'keep'|'force'`) Decides what to do if a key is
found in more than one map:

View File

@ -42,6 +42,9 @@ DIAGNOSTICS
• |vim.diagnostic.config()| accepts a "jump" table to specify defaults for
|vim.diagnostic.jump()|.
• Diagnostics are filtered by severity before being passed to a diagnostic
handler |diagnostic-handlers|.
EDITOR
@ -214,9 +217,6 @@ These existing features changed their behavior.
more emoji characters than before, including those encoded with multiple
emoji codepoints combined with ZWJ (zero width joiner) codepoints.
• |vim.tbl_deep_extend()| no longer ignores any values for which |vim.isarray()|
returns `true`.
==============================================================================
REMOVED FEATURES *news-removed*

View File

@ -53,11 +53,17 @@ active yes yes 'a'
hidden no yes 'h'
inactive no no ' '
Note: All CTRL-W commands can also be executed with |:wincmd|, for those
places where a Normal mode command can't be used or is inconvenient.
*buffer-reuse*
Each buffer has a unique number and the number will not change within a Vim
session. The |bufnr()| and |bufname()| functions can be used to convert
between a buffer name and the buffer number. There is one exception: if a new
empty buffer is created and it is not modified, the buffer will be re-used
when loading another file into that buffer. This also means the buffer number
will not change.
The main Vim window can hold several split windows. There are also tab pages
|tab-page|, each of which can hold multiple windows.
*window-ID* *winid* *windowid*
Each window has a unique identifier called the window ID. This identifier
will not change within a Vim session. The |win_getid()| and |win_id2tabwin()|
@ -69,9 +75,6 @@ across tabs. For most functions that take a window ID or a window number, the
window number only applies to the current tab, while the window ID can refer
to a window in any tab.
Each buffer has a unique number and the number will not change within a Vim
session. The |bufnr()| and |bufname()| functions can be used to convert
between a buffer name and the buffer number.
==============================================================================
2. Starting Vim *windows-starting*
@ -468,6 +471,10 @@ These commands can also be executed with ":wincmd":
:exe nr .. "wincmd w"
< This goes to window "nr".
Note: All CTRL-W commands can also be executed with |:wincmd|, for those
places where a Normal mode command can't be used or is inconvenient (e.g.
in a browser-based terminal).
==============================================================================
5. Moving windows around *window-moving*

3
runtime/ftplugin/dot.lua Normal file
View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n setl commentstring<'

View File

@ -0,0 +1,3 @@
vim.bo.commentstring = '// %s'
vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | setl commentstring<'

View File

@ -1293,9 +1293,25 @@ local function opt_to_global_state(opt, title)
local fonts = {}
if opt.font then
fonts = type(opt.font) == 'string' and { opt.font } or opt.font --[[@as (string[])]]
for i, v in pairs(fonts) do
fonts[i] = ('"%s"'):format(v)
end
elseif vim.o.guifont:match('^[^:]+') then
table.insert(fonts, vim.o.guifont:match('^[^:]+'))
-- Example:
-- Input: "Font,Escape\,comma, Ignore space after comma"
-- Output: { "Font","Escape,comma","Ignore space after comma" }
local prev = ''
for name in vim.gsplit(vim.o.guifont:match('^[^:]+'), ',', { trimempty = true }) do
if vim.endswith(name, '\\') then
prev = prev .. vim.trim(name:sub(1, -2) .. ',')
elseif vim.trim(name) ~= '' then
table.insert(fonts, ('"%s%s"'):format(prev, vim.trim(name)))
prev = ''
end
end
end
-- Generic family names (monospace here) must not be quoted
-- because the browser recognizes them as font families.
table.insert(fonts, 'monospace')
--- @type vim.tohtml.state.global
local state = {

View File

@ -1366,10 +1366,6 @@ M.handlers.signs = {
return
end
if opts.signs and opts.signs.severity then
diagnostics = filter_by_severity(opts.signs.severity, diagnostics)
end
-- 10 is the default sign priority when none is explicitly specified
local priority = opts.signs and opts.signs.priority or 10
local get_priority --- @type function
@ -1487,16 +1483,11 @@ M.handlers.underline = {
})
bufnr = get_bufnr(bufnr)
opts = opts or {}
if not vim.api.nvim_buf_is_loaded(bufnr) then
return
end
if opts.underline and opts.underline.severity then
diagnostics = filter_by_severity(opts.underline.severity, diagnostics)
end
local ns = M.get_namespace(namespace)
if not ns.user_data.underline_ns then
ns.user_data.underline_ns =
@ -1566,7 +1557,6 @@ M.handlers.virtual_text = {
return
end
local severity --- @type vim.diagnostic.SeverityFilter?
if opts.virtual_text then
if opts.virtual_text.format then
diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
@ -1577,9 +1567,6 @@ M.handlers.virtual_text = {
then
diagnostics = prefix_source(diagnostics)
end
if opts.virtual_text.severity then
severity = opts.virtual_text.severity
end
end
local ns = M.get_namespace(namespace)
@ -1591,9 +1578,6 @@ M.handlers.virtual_text = {
local virt_text_ns = ns.user_data.virt_text_ns
local buffer_line_diagnostics = diagnostic_lines(diagnostics)
for line, line_diagnostics in pairs(buffer_line_diagnostics) do
if severity then
line_diagnostics = filter_by_severity(severity, line_diagnostics)
end
local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text)
if virt_texts then
@ -1808,7 +1792,8 @@ function M.show(namespace, bufnr, diagnostics, opts)
for handler_name, handler in pairs(M.handlers) do
if handler.show and opts_res[handler_name] then
handler.show(namespace, bufnr, diagnostics, opts_res)
local filtered = filter_by_severity(opts_res[handler_name].severity, diagnostics)
handler.show(namespace, bufnr, filtered, opts_res)
end
end
end

View File

@ -77,12 +77,7 @@ function M.on_inlayhint(err, result, ctx, _)
local col = position.character
if col > 0 then
local line = lines[position.line + 1] or ''
local ok, convert_result
ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding)
if ok then
return convert_result
end
return math.min(#line, col)
return util._str_byteindex_enc(line, col, client.offset_encoding)
end
return col
end

View File

@ -140,12 +140,7 @@ local function tokens_to_ranges(data, bufnr, client, request)
local function _get_byte_pos(col)
if col > 0 then
local buf_line = lines[line + 1] or ''
local ok, result
ok, result = pcall(util._str_byteindex_enc, buf_line, col, client.offset_encoding)
if ok then
return result
end
return math.min(#buf_line, col)
return util._str_byteindex_enc(buf_line, col, client.offset_encoding)
end
return col
end

View File

@ -119,6 +119,7 @@ end
---@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16
---@return integer `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
local len32, len16 = vim.str_utfindex(line)
if not encoding then
encoding = 'utf-16'
end
@ -129,9 +130,15 @@ function M._str_utfindex_enc(line, index, encoding)
return #line
end
elseif encoding == 'utf-16' then
if not index or index > len16 then
return len16
end
local _, col16 = vim.str_utfindex(line, index)
return col16
elseif encoding == 'utf-32' then
if not index or index > len32 then
return len32
end
local col32, _ = vim.str_utfindex(line, index)
return col32
else
@ -147,6 +154,12 @@ end
---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16
---@return integer byte (utf-8) index of `encoding` index `index` in `line`
function M._str_byteindex_enc(line, index, encoding)
local len = vim.fn.strlen(line)
if index > len then
-- LSP spec: if character > line length, default to the line length.
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
return len
end
if not encoding then
encoding = 'utf-16'
end
@ -165,9 +178,6 @@ function M._str_byteindex_enc(line, index, encoding)
end
end
local _str_utfindex_enc = M._str_utfindex_enc
local _str_byteindex_enc = M._str_byteindex_enc
--- Replaces text in a range with new text.
---
--- CAUTION: Changes in-place!
@ -334,12 +344,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- character
if col > 0 then
local line = get_line(bufnr, position.line) or ''
local ok, result
ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding)
if ok then
return result
end
return math.min(#line, col)
return M._str_byteindex_enc(line, col, offset_encoding or 'utf-16')
end
return col
end
@ -436,14 +441,15 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
e.end_col = last_line_len
has_eol_text_edit = true
else
-- If the replacement is over the end of a line (i.e. e.end_col is out of bounds and the
-- If the replacement is over the end of a line (i.e. e.end_col is equal to the line length and the
-- replacement text ends with a newline We can likely assume that the replacement is assumed
-- to be meant to replace the newline with another newline and we need to make sure this
-- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r'
-- in the file some servers (clangd on windows) will include that character in the line
-- while nvim_buf_set_text doesn't count it as part of the line.
if
e.end_col > last_line_len
e.end_col >= last_line_len
and text_edit.range['end'].character > e.end_col
and #text_edit.newText > 0
and string.sub(text_edit.newText, -1) == '\n'
then
@ -1795,17 +1801,9 @@ function M.locations_to_items(locations, offset_encoding)
local row = pos.line
local end_row = end_pos.line
local line = lines[row] or ''
local line_len = vim.fn.strcharlen(line)
local end_line = lines[end_row] or ''
local end_line_len = vim.fn.strcharlen(end_line)
-- LSP spec: if character > line length, default to the line length.
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
local col = pos.character <= line_len
and M._str_byteindex_enc(line, pos.character, offset_encoding)
or line_len
local end_col = end_pos.character <= end_line_len
and M._str_byteindex_enc(end_line, end_pos.character, offset_encoding)
or end_line_len
local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
local end_col = M._str_byteindex_enc(end_line, end_pos.character, offset_encoding)
table.insert(items, {
filename = filename,
@ -1935,7 +1933,7 @@ local function make_position_param(window, offset_encoding)
return { line = 0, character = 0 }
end
col = _str_utfindex_enc(line, col, offset_encoding)
col = M._str_utfindex_enc(line, col, offset_encoding)
return { line = row, character = col }
end
@ -2117,11 +2115,7 @@ function M.character_offset(buf, row, col, offset_encoding)
)
offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding
end
-- If the col is past the EOL, use the line length.
if col > #line then
return _str_utfindex_enc(line, nil, offset_encoding)
end
return _str_utfindex_enc(line, col, offset_encoding)
return M._str_utfindex_enc(line, col, offset_encoding)
end
--- Helper function to return nested values in language server settings

View File

@ -354,6 +354,12 @@ function vim.tbl_isempty(t)
return next(t) == nil
end
--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers
--- starting from 1)
local function can_merge(v)
return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v))
end
--- Recursive worker for tbl_extend
--- @param behavior 'error'|'keep'|'force'
--- @param deep_extend boolean
@ -368,7 +374,7 @@ local function tbl_extend_rec(behavior, deep_extend, ...)
local tbl = select(i, ...) --[[@as table<any,any>]]
if tbl then
for k, v in pairs(tbl) do
if deep_extend and type(v) == 'table' and type(ret[k]) == 'table' then
if deep_extend and can_merge(v) and can_merge(ret[k]) then
ret[k] = tbl_extend_rec(behavior, true, ret[k], v)
elseif behavior ~= 'force' and ret[k] ~= nil then
if behavior == 'error' then
@ -421,6 +427,11 @@ end
--- Merges recursively two or more tables.
---
--- Only values that are empty tables or tables that are not |lua-list|s (indexed by consecutive
--- integers starting from 1) are merged recursively. This is useful for merging nested tables
--- like default and user configurations where lists should be treated as literals (i.e., are
--- overwritten instead of merged).
---
---@see |vim.tbl_extend()|
---
---@generic T1: table

View File

@ -152,14 +152,14 @@ function M.open(path)
else
return nil, 'vim.ui.open: rundll32 not found'
end
elseif vim.fn.executable('wslview') == 1 then
cmd = { 'wslview', path }
elseif vim.fn.executable('explorer.exe') == 1 then
cmd = { 'explorer.exe', path }
elseif vim.fn.executable('xdg-open') == 1 then
cmd = { 'xdg-open', path }
opts.stdout = false
opts.stderr = false
elseif vim.fn.executable('wslview') == 1 then
cmd = { 'wslview', path }
elseif vim.fn.executable('explorer.exe') == 1 then
cmd = { 'explorer.exe', path }
else
return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)'
end

View File

@ -7641,7 +7641,7 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
return;
}
const void *iter = NULL;
const char *appname = get_appname();
const char *appname = get_appname(false);
do {
size_t dir_len;
const char *dir;

View File

@ -16,15 +16,14 @@ struct loop {
uv_loop_t uv;
MultiQueue *events;
MultiQueue *thread_events;
// Immediate events:
// "Processed after exiting uv_run() (to avoid recursion), but before
// returning from loop_poll_events()." 502aee690c98
// Practical consequence (for main_loop): these events are processed by
// state_enter()..os_inchar()
// whereas "regular" events (main_loop.events) are processed by
// state_enter()..VimState.execute()
// But state_enter()..os_inchar() can be "too early" if you want the event
// to trigger UI updates and other user-activity-related side-effects.
// Immediate events.
// - "Processed after exiting `uv_run()` (to avoid recursion), but before returning from
// `loop_poll_events()`." 502aee690c98
// - Practical consequence (for `main_loop`):
// - these are processed by `state_enter()..input_get()` whereas "regular" events
// (`main_loop.events`) are processed by `state_enter()..VimState.execute()`
// - `state_enter()..input_get()` can be "too early" if you want the event to trigger UI
// updates and other user-activity-related side-effects.
MultiQueue *fast_events;
// used by process/job-control subsystem

View File

@ -3264,18 +3264,12 @@ static void vim_mktempdir(void)
char tmp[TEMP_FILE_PATH_MAXLEN];
char path[TEMP_FILE_PATH_MAXLEN];
char user[40] = { 0 };
char appname[40] = { 0 };
os_get_username(user, sizeof(user));
// Usernames may contain slashes! #19240
memchrsub(user, '/', '_', sizeof(user));
memchrsub(user, '\\', '_', sizeof(user));
// Appname may be a relative path, replace slashes to make it name-like.
xstrlcpy(appname, get_appname(), sizeof(appname));
memchrsub(appname, '/', '%', sizeof(appname));
memchrsub(appname, '\\', '%', sizeof(appname));
// Make sure the umask doesn't remove the executable bit.
// "repl" has been reported to use "0177".
mode_t umask_save = umask(0077);
@ -3283,14 +3277,15 @@ static void vim_mktempdir(void)
// Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999".
expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64);
if (!os_isdir(tmp)) {
if (strequal("$TMPDIR", temp_dirs[i])) {
WLOG("$TMPDIR tempdir not a directory (or does not exist): %s", tmp);
}
continue;
}
// "/tmp/" exists, now try to create "/tmp/nvim.<user>/".
add_pathsep(tmp);
xstrlcat(tmp, appname, sizeof(tmp));
xstrlcat(tmp, ".", sizeof(tmp));
xstrlcat(tmp, "nvim.", sizeof(tmp));
xstrlcat(tmp, user, sizeof(tmp));
os_mkdir(tmp, 0700); // Always create, to avoid a race.
bool owned = os_file_owned(tmp);

View File

@ -1858,7 +1858,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (!char_avail()) {
// Flush screen updates before blocking.
ui_flush();
os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
continue;
@ -2981,7 +2981,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
uint8_t dum[DUM_LEN + 1];
while (true) {
len = os_inchar(dum, DUM_LEN, 0, 0, NULL);
len = input_get(dum, DUM_LEN, 0, 0, NULL);
if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) {
break;
}
@ -2997,7 +2997,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
// Fill up to a third of the buffer, because each character may be
// tripled below.
len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
len = input_get(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
}
// If the typebuf was changed further down, it is like nothing was added by

View File

@ -370,12 +370,15 @@ void update_window_hl(win_T *wp, bool invalid)
// determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_config.external;
if (float_win && hl_def[HLF_NFLOAT] != 0) {
if (float_win && hl_def[HLF_NFLOAT] != 0 && ns_id > 0) {
wp->w_hl_attr_normal = hl_def[HLF_NFLOAT];
} else if (hl_def[HLF_COUNT] > 0) {
wp->w_hl_attr_normal = hl_def[HLF_COUNT];
} else if (float_win) {
wp->w_hl_attr_normal = HL_ATTR(HLF_NFLOAT) > 0
? HL_ATTR(HLF_NFLOAT) : highlight_attr[HLF_NFLOAT];
} else {
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
wp->w_hl_attr_normal = 0;
}
if (wp->w_floating) {

View File

@ -37,7 +37,7 @@
/// @param[in] str Prompt: question to ask user. Is always followed by
/// " (y/n)?".
/// @param[in] direct Determines what function to use to get user input. If
/// true then os_inchar() will be used, otherwise vgetc().
/// true then input_get() will be used, otherwise vgetc().
/// I.e. when direct is true then characters are obtained
/// directly from the user without buffers involved.
///
@ -111,7 +111,7 @@ int get_keystroke(MultiQueue *events)
// First time: blocking wait. Second time: wait up to 100ms for a
// terminal code to complete.
n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
n = input_get(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
if (n > 0) {
// Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);

View File

@ -266,7 +266,7 @@ int main(int argc, char **argv)
if (argc > 1 && STRICMP(argv[1], "-ll") == 0) {
if (argc == 2) {
print_mainerr(err_arg_missing, argv[1]);
print_mainerr(err_arg_missing, argv[1], NULL);
exit(1);
}
nlua_run_script(argv, argc, 3);
@ -357,10 +357,8 @@ int main(int argc, char **argv)
assert(!ui_client_channel_id && !use_builtin_ui);
// Nvim server...
int listen_rv = server_init(params.listen_addr);
if (listen_rv != 0) {
mainerr("Failed to --listen", listen_rv < 0
? os_strerror(listen_rv) : (listen_rv == 1 ? "empty address" : NULL));
if (!server_init(params.listen_addr)) {
mainerr(IObuff, NULL, NULL);
}
TIME_MSG("expanding arguments");
@ -1053,7 +1051,7 @@ static void command_line_scan(mparm_T *parmp)
// "+" or "+{number}" or "+/{pat}" or "+{command}" argument.
if (argv[0][0] == '+' && !had_minmin) {
if (parmp->n_commands >= MAX_ARG_CMDS) {
mainerr(err_extra_cmd, NULL);
mainerr(err_extra_cmd, NULL, NULL);
}
argv_idx = -1; // skip to next argument
if (argv[0][1] == NUL) {
@ -1074,7 +1072,7 @@ static void command_line_scan(mparm_T *parmp)
parmp->no_swap_file = true;
} else {
if (parmp->edit_type > EDIT_STDIN) {
mainerr(err_too_many_args, argv[0]);
mainerr(err_too_many_args, argv[0], NULL);
}
parmp->had_stdin_file = true;
parmp->edit_type = EDIT_STDIN;
@ -1137,7 +1135,7 @@ static void command_line_scan(mparm_T *parmp)
nlua_disable_preload = true;
} else {
if (argv[0][argv_idx]) {
mainerr(err_opt_unknown, argv[0]);
mainerr(err_opt_unknown, argv[0], NULL);
}
had_minmin = true;
}
@ -1211,7 +1209,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 'q': // "-q" QuickFix mode
if (parmp->edit_type != EDIT_NONE) {
mainerr(err_too_many_args, argv[0]);
mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_QF;
if (argv[0][argv_idx]) { // "-q{errorfile}"
@ -1240,7 +1238,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 't': // "-t {tag}" or "-t{tag}" jump to tag
if (parmp->edit_type != EDIT_NONE) {
mainerr(err_too_many_args, argv[0]);
mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_TAG;
if (argv[0][argv_idx]) { // "-t{tag}"
@ -1274,7 +1272,7 @@ static void command_line_scan(mparm_T *parmp)
case 'c': // "-c{command}" or "-c {command}" exec command
if (argv[0][argv_idx] != NUL) {
if (parmp->n_commands >= MAX_ARG_CMDS) {
mainerr(err_extra_cmd, NULL);
mainerr(err_extra_cmd, NULL, NULL);
}
parmp->commands[parmp->n_commands++] = argv[0] + argv_idx;
argv_idx = -1;
@ -1291,19 +1289,19 @@ static void command_line_scan(mparm_T *parmp)
break;
default:
mainerr(err_opt_unknown, argv[0]);
mainerr(err_opt_unknown, argv[0], NULL);
}
// Handle option arguments with argument.
if (want_argument) {
// Check for garbage immediately after the option letter.
if (argv[0][argv_idx] != NUL) {
mainerr(err_opt_garbage, argv[0]);
mainerr(err_opt_garbage, argv[0], NULL);
}
argc--;
if (argc < 1 && c != 'S') { // -S has an optional argument
mainerr(err_arg_missing, argv[0]);
mainerr(err_arg_missing, argv[0], NULL);
}
argv++;
argv_idx = -1;
@ -1312,7 +1310,7 @@ static void command_line_scan(mparm_T *parmp)
case 'c': // "-c {command}" execute command
case 'S': // "-S {file}" execute Vim script
if (parmp->n_commands >= MAX_ARG_CMDS) {
mainerr(err_extra_cmd, NULL);
mainerr(err_extra_cmd, NULL, NULL);
}
if (c == 'S') {
char *a;
@ -1343,7 +1341,7 @@ static void command_line_scan(mparm_T *parmp)
if (strequal(argv[-1], "--cmd")) {
// "--cmd {command}" execute command
if (parmp->n_pre_commands >= MAX_ARG_CMDS) {
mainerr(err_extra_cmd, NULL);
mainerr(err_extra_cmd, NULL, NULL);
}
parmp->pre_commands[parmp->n_pre_commands++] = argv[0];
} else if (strequal(argv[-1], "--listen")) {
@ -1425,7 +1423,7 @@ scripterror:
// Check for only one type of editing.
if (parmp->edit_type > EDIT_STDIN) {
mainerr(err_too_many_args, argv[0]);
mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_FILE;
@ -1472,7 +1470,7 @@ scripterror:
}
if (embedded_mode && (silent_mode || parmp->luaf)) {
mainerr(_("--embed conflicts with -es/-Es/-l"), NULL);
mainerr(_("--embed conflicts with -es/-Es/-l"), NULL, NULL);
}
// If there is a "+123" or "-c" command, set v:swapcommand to the first one.
@ -2135,28 +2133,30 @@ static int execute_env(char *env)
return OK;
}
/// Prints the following then exits:
/// - An error message `errstr`
/// - A string `str` if not null
/// Prints a message of the form "{msg1}: {msg2}: {msg3}", then exits with code 1.
///
/// @param errstr string containing an error message
/// @param str string to append to the primary error message, or NULL
static void mainerr(const char *errstr, const char *str)
/// @param msg1 error message
/// @param msg2 extra message, or NULL
/// @param msg3 extra message, or NULL
static void mainerr(const char *msg1, const char *msg2, const char *msg3)
FUNC_ATTR_NORETURN
{
print_mainerr(errstr, str);
print_mainerr(msg1, msg2, msg3);
os_exit(1);
}
static void print_mainerr(const char *errstr, const char *str)
static void print_mainerr(const char *msg1, const char *msg2, const char *msg3)
{
char *prgname = path_tail(argv0);
signal_stop(); // kill us with CTRL-C here, if you like
fprintf(stderr, "%s: %s", prgname, _(errstr));
if (str != NULL) {
fprintf(stderr, ": \"%s\"", str);
fprintf(stderr, "%s: %s", prgname, _(msg1));
if (msg2 != NULL) {
fprintf(stderr, ": \"%s\"", msg2);
}
if (msg3 != NULL) {
fprintf(stderr, ": \"%s\"", msg3);
}
fprintf(stderr, _("\nMore info with \""));
fprintf(stderr, "%s -h\"\n", prgname);

View File

@ -12,7 +12,7 @@
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
/// of input_get(), so they are processed "just-in-time".
EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -11,12 +11,14 @@
#include "nvim/event/socket.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/types_defs.h"
#define MAX_CONNECTIONS 32
#define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated
@ -27,36 +29,30 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE;
# include "msgpack_rpc/server.c.generated.h"
#endif
/// Initializes the module
/// Initializes resources, handles `--listen`, starts the primary server at v:servername.
///
/// @returns 0: success, 1: validation error, 2: already listening, -errno: failed to bind/listen.
int server_init(const char *listen_addr)
/// @returns true on success, false on fatal error (message stored in IObuff)
bool server_init(const char *listen_addr)
{
bool ok = true;
bool must_free = false;
TriState user_arg = kTrue; // User-provided --listen arg.
ga_init(&watchers, sizeof(SocketWatcher *), 1);
// $NVIM_LISTEN_ADDRESS (deprecated)
if ((!listen_addr || listen_addr[0] == '\0') && os_env_exists(ENV_LISTEN)) {
user_arg = kFalse; // User-provided env var.
listen_addr = os_getenv(ENV_LISTEN);
}
if (!listen_addr || listen_addr[0] == '\0') {
user_arg = kNone; // Autogenerated server address.
listen_addr = server_address_new(NULL);
must_free = true;
}
if (!listen_addr) {
abort(); // Cannot happen.
}
int rv = server_start(listen_addr);
if (os_env_exists(ENV_LISTEN)) {
// Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. It is "input only", it must not be
// leaked to child jobs or :terminal.
os_unsetenv(ENV_LISTEN);
}
// TODO(justinmk): this is for logging_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG")) {
ELOG("test log message");
@ -66,7 +62,28 @@ int server_init(const char *listen_addr)
xfree((char *)listen_addr);
}
return rv;
if (rv == 0 || user_arg == kNone) {
// The autogenerated servername can fail if the user has a broken $XDG_RUNTIME_DIR. #30282
// But that is not fatal (startup will continue, logged in $NVIM_LOGFILE, empty v:servername).
goto end;
}
(void)snprintf(IObuff, IOSIZE,
user_arg ==
kTrue ? "Failed to --listen: %s: \"%s\""
: "Failed $NVIM_LISTEN_ADDRESS: %s: \"%s\"",
rv < 0 ? os_strerror(rv) : (rv == 1 ? "empty address" : "?"),
listen_addr);
ok = false;
end:
if (os_env_exists(ENV_LISTEN)) {
// Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. It is "input only", it must not be
// leaked to child jobs or :terminal.
os_unsetenv(ENV_LISTEN);
}
return ok;
}
/// Teardown a single server
@ -97,17 +114,19 @@ void server_teardown(void)
/// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
/// - Other: "/tmp/nvim.user/xxx/<name>.<pid>.<counter>"
char *server_address_new(const char *name)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
static uint32_t count = 0;
char fmt[ADDRESS_MAX_SIZE];
const char *appname = get_appname();
#ifdef MSWIN
(void)get_appname(true);
int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
name ? name : appname, os_get_pid(), count++);
name ? name : NameBuff, os_get_pid(), count++);
#else
char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
(void)get_appname(true);
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
dir, name ? name : appname, os_get_pid(), count++);
dir, name ? name : NameBuff, os_get_pid(), count++);
xfree(dir);
#endif
if ((size_t)r >= sizeof(fmt)) {

View File

@ -6597,11 +6597,11 @@ static void nv_open(cmdarg_T *cap)
static void nv_event(cmdarg_T *cap)
{
// Garbage collection should have been executed before blocking for events in
// the `os_inchar` in `state_enter`, but we also disable it here in case the
// `os_inchar` branch was not executed (!multiqueue_empty(loop.events), which
// the `input_get` in `state_enter`, but we also disable it here in case the
// `input_get` branch was not executed (!multiqueue_empty(loop.events), which
// could have `may_garbage_collect` set to true in `normal_check`).
//
// That is because here we may run code that calls `os_inchar`
// That is because here we may run code that calls `input_get`
// later(`f_confirm` or `get_keystroke` for example), but in these cases it is
// not safe to perform garbage collection because there could be unreferenced
// lists or dicts being used.

View File

@ -33,12 +33,6 @@
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN)
typedef enum {
kInputNone,
kInputAvail,
kInputEof,
} InbufPollResult;
static RStream read_stream = { .s.closed = true }; // Input before UI starts.
static char input_buffer[INPUT_BUFFER_SIZE];
static char *input_read_pos = input_buffer;
@ -84,57 +78,76 @@ static void cursorhold_event(void **argv)
static void create_cursorhold_event(bool events_enabled)
{
// If events are enabled and the queue has any items, this function should not
// have been called (inbuf_poll would return kInputAvail).
// have been called (`inbuf_poll` would return `kTrue`).
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
assert(!events_enabled || multiqueue_empty(main_loop.events));
multiqueue_put(main_loop.events, cursorhold_event, NULL);
}
static void restart_cursorhold_wait(int tb_change_cnt)
static void reset_cursorhold_wait(int tb_change_cnt)
{
cursorhold_time = 0;
cursorhold_tb_change_cnt = tb_change_cnt;
}
/// Low level input function
/// Reads OS input into `buf`, and consumes pending events while waiting (if `ms != 0`).
///
/// Wait until either the input buffer is non-empty or, if `events` is not NULL
/// until `events` is non-empty.
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
/// - Consumes available input received from the OS.
/// - Consumes pending events.
/// - Manages CursorHold events.
/// - Handles EOF conditions.
///
/// Originally based on the Vim `mch_inchar` function.
///
/// @param buf Buffer to store read input.
/// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading.
/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
/// @param tb_change_cnt Used to detect when typeahead changes.
/// @param events (optional) Events to process.
/// @return Bytes read into buf, or 0 if no input was read
int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
{
// This check is needed so that feeding typeahead from RPC can prevent CursorHold.
if (tb_change_cnt != cursorhold_tb_change_cnt) {
restart_cursorhold_wait(tb_change_cnt);
reset_cursorhold_wait(tb_change_cnt);
}
if (maxlen && input_available()) {
restart_cursorhold_wait(tb_change_cnt);
size_t to_read = MIN((size_t)maxlen, input_available());
memcpy(buf, input_read_pos, to_read);
input_read_pos += to_read;
return (int)to_read;
}
#define TRY_READ() \
do { \
if (maxlen && input_available()) { \
reset_cursorhold_wait(tb_change_cnt); \
assert(maxlen >= 0); \
size_t to_read = MIN((size_t)maxlen, input_available()); \
memcpy(buf, input_read_pos, to_read); \
input_read_pos += to_read; \
/* This is safe because INPUT_BUFFER_SIZE fits in an int. */ \
assert(to_read <= INT_MAX); \
return (int)to_read; \
} \
} while (0)
TRY_READ();
// No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped.
if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) {
ctrl_c_interrupts = false;
}
InbufPollResult result;
TriState result; ///< inbuf_poll result.
if (ms >= 0) {
if ((result = inbuf_poll(ms, events)) == kInputNone) {
if ((result = inbuf_poll(ms, events)) == kFalse) {
return 0;
}
} else {
uint64_t wait_start = os_hrtime();
cursorhold_time = MIN(cursorhold_time, (int)p_ut);
if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) {
if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kFalse) {
if (read_stream.s.closed && silent_mode) {
// Drained eventloop & initial input; exit silent/batch-mode (-es/-Es).
read_error_exit();
}
restart_cursorhold_wait(tb_change_cnt);
reset_cursorhold_wait(tb_change_cnt);
if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) {
create_cursorhold_event(events == main_loop.events);
} else {
@ -153,32 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
return 0;
}
if (maxlen && input_available()) {
restart_cursorhold_wait(tb_change_cnt);
// Safe to convert `to_read` to int, it will never overflow since
// INPUT_BUFFER_SIZE fits in an int
size_t to_read = MIN((size_t)maxlen, input_available());
memcpy(buf, input_read_pos, to_read);
input_read_pos += to_read;
return (int)to_read;
}
TRY_READ();
// If there are events, return the keys directly
if (maxlen && pending_events(events)) {
return push_event_key(buf, maxlen);
}
if (result == kInputEof) {
if (result == kNone) {
read_error_exit();
}
return 0;
#undef TRY_READ
}
// Check if a character is available for reading
bool os_char_avail(void)
{
return inbuf_poll(0, NULL) == kInputAvail;
return inbuf_poll(0, NULL) == kTrue;
}
/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
@ -463,15 +470,22 @@ bool input_blocking(void)
return blocking;
}
// This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
/// Checks for (but does not read) available input, and consumes `main_loop.events` while waiting.
///
/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
/// @param events (optional) Queue to check for pending events.
/// @return TriState:
/// - kTrue: Input/events available
/// - kFalse: No input/events
/// - kNone: EOF reached on the input stream
static TriState inbuf_poll(int ms, MultiQueue *events)
{
if (os_input_ready(events)) {
return kInputAvail;
return kTrue;
}
if (do_profiling == PROF_YES && ms) {
prof_inchar_enter();
prof_input_start();
}
if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) {
@ -479,20 +493,18 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
blocking = true;
multiqueue_process_events(ch_before_blocking_events);
}
DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
events && !multiqueue_empty(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
os_input_ready(events) || input_eof);
DLOG("blocking... events=%d pending=%d", !!events, pending_events(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof);
blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
prof_input_end();
}
if (os_input_ready(events)) {
return kInputAvail;
return kTrue;
}
return input_eof ? kInputEof : kInputNone;
return input_eof ? kNone : kFalse;
}
static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof)
@ -533,8 +545,8 @@ static void process_ctrl_c(void)
}
}
// Helper function used to push bytes from the 'event' key sequence partially
// between calls to os_inchar when maxlen < 3
/// Pushes bytes from the "event" key sequence (KE_EVENT) partially between calls to input_get when
/// `maxlen < 3`.
static int push_event_key(uint8_t *buf, int maxlen)
{
static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT };

View File

@ -63,22 +63,32 @@ static const char *const xdg_defaults[] = {
#endif
};
/// Get the value of $NVIM_APPNAME or "nvim" if not set.
/// Gets the value of $NVIM_APPNAME, or "nvim" if not set.
///
/// @param namelike Write "name-like" value (no path separators) in `NameBuff`.
///
/// @return $NVIM_APPNAME value
const char *get_appname(void)
const char *get_appname(bool namelike)
{
const char *env_val = os_getenv("NVIM_APPNAME");
if (env_val == NULL || *env_val == NUL) {
env_val = "nvim";
}
if (namelike) {
// Appname may be a relative path, replace slashes to make it name-like.
xstrlcpy(NameBuff, env_val, sizeof(NameBuff));
memchrsub(NameBuff, '/', '-', sizeof(NameBuff));
memchrsub(NameBuff, '\\', '-', sizeof(NameBuff));
}
return env_val;
}
/// Ensure that APPNAME is valid. Must be a name or relative path.
bool appname_is_valid(void)
{
const char *appname = get_appname();
const char *appname = get_appname(false);
if (path_is_absolute(appname)
// TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it?
|| strequal(appname, "/")
@ -193,7 +203,7 @@ char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
char *dir = stdpaths_get_xdg_var(idx);
const char *appname = get_appname();
const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
assert(appname_len < (IOSIZE - sizeof("-data")));

View File

@ -383,19 +383,19 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg)
xp->xp_context = EXPAND_NOTHING;
}
static proftime_T inchar_time;
static proftime_T wait_time;
/// Called when starting to wait for the user to type a character.
void prof_inchar_enter(void)
void prof_input_start(void)
{
inchar_time = profile_start();
wait_time = profile_start();
}
/// Called when finished waiting for the user to type a character.
void prof_inchar_exit(void)
void prof_input_end(void)
{
inchar_time = profile_end(inchar_time);
profile_set_wait(profile_add(profile_get_wait(), inchar_time));
wait_time = profile_end(wait_time);
profile_set_wait(profile_add(profile_get_wait(), wait_time));
}
/// @return true when a function defined in the current script should be

View File

@ -1559,7 +1559,7 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
return dest;
}
const void *iter = NULL;
const char *appname = get_appname();
const char *appname = get_appname(false);
const size_t appname_len = strlen(appname);
do {
size_t dir_len;
@ -1626,7 +1626,7 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
const char *appname = get_appname();
const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
assert(appname_len < (IOSIZE - sizeof("-data")));
xmemcpyz(IObuff, appname, appname_len);
@ -1697,7 +1697,7 @@ char *runtimepath_default(bool clean_arg)
size_t config_len = 0;
size_t vimruntime_len = 0;
size_t libdir_len = 0;
const char *appname = get_appname();
const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
if (data_home != NULL) {
data_len = strlen(data_home);

View File

@ -74,10 +74,9 @@ getkey:
}
// Flush screen updates before blocking.
ui_flush();
// Call `os_inchar` directly to block for events or user input without
// consuming anything from `input_buffer`(os/input.c) or calling the
// mapping engine.
os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// Call `input_get` directly to block for events or user input without consuming anything from
// `os/input.c:input_buffer` or calling the mapping engine.
input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
if (!input_available() && !multiqueue_empty(main_loop.events)) {
key = K_EVENT;

View File

@ -3201,7 +3201,7 @@ describe('API', function()
end)
describe('nvim_get_runtime_file', function()
local p = n.alter_slashes
local p = t.fix_slashes
it('can find files', function()
eq({}, api.nvim_get_runtime_file('bork.borkbork', false))
eq({}, api.nvim_get_runtime_file('bork.borkbork', true))
@ -3210,36 +3210,36 @@ describe('API', function()
local val = api.nvim_get_runtime_file('autoload/remote/*.vim', true)
eq(2, #val)
if endswith(val[1], 'define.vim') then
ok(endswith(val[1], p 'autoload/remote/define.vim'))
ok(endswith(val[2], p 'autoload/remote/host.vim'))
ok(endswith(p(val[1]), 'autoload/remote/define.vim'))
ok(endswith(p(val[2]), 'autoload/remote/host.vim'))
else
ok(endswith(val[1], p 'autoload/remote/host.vim'))
ok(endswith(val[2], p 'autoload/remote/define.vim'))
ok(endswith(p(val[1]), 'autoload/remote/host.vim'))
ok(endswith(p(val[2]), 'autoload/remote/define.vim'))
end
val = api.nvim_get_runtime_file('autoload/remote/*.vim', false)
eq(1, #val)
ok(
endswith(val[1], p 'autoload/remote/define.vim')
or endswith(val[1], p 'autoload/remote/host.vim')
endswith(p(val[1]), 'autoload/remote/define.vim')
or endswith(p(val[1]), 'autoload/remote/host.vim')
)
val = api.nvim_get_runtime_file('lua', true)
eq(1, #val)
ok(endswith(val[1], p 'lua'))
ok(endswith(p(val[1]), 'lua'))
val = api.nvim_get_runtime_file('lua/vim', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/vim'))
ok(endswith(p(val[1]), 'lua/vim'))
end)
it('can find directories', function()
local val = api.nvim_get_runtime_file('lua/', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/'))
ok(endswith(p(val[1]), 'lua/'))
val = api.nvim_get_runtime_file('lua/vim/', true)
eq(1, #val)
ok(endswith(val[1], p 'lua/vim/'))
ok(endswith(p(val[1]), 'lua/vim/'))
eq({}, api.nvim_get_runtime_file('foobarlang/', true))
end)

View File

@ -9,7 +9,7 @@ local request = n.request
local is_os = t.is_os
describe('autocmd DirChanged and DirChangedPre', function()
local curdir = vim.uv.cwd():gsub('\\', '/')
local curdir = t.fix_slashes(vim.uv.cwd())
local dirs = {
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',

View File

@ -321,11 +321,11 @@ end)
describe('tmpdir', function()
local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=]
local testlog = 'Xtest_tmpdir_log'
local os_tmpdir
local os_tmpdir ---@type string
before_each(function()
-- Fake /tmp dir so that we can mess it up.
os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')
os_tmpdir = assert(vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX'))
end)
after_each(function()
@ -414,15 +414,4 @@ describe('tmpdir', function()
rm_tmpdir()
eq('E5431: tempdir disappeared (3 times)', api.nvim_get_vvar('errmsg'))
end)
it('$NVIM_APPNAME relative path', function()
clear({
env = {
NVIM_APPNAME = 'a/b',
NVIM_LOG_FILE = testlog,
TMPDIR = os_tmpdir,
},
})
matches([=[.*[/\\]a%%b%.[^/\\]+]=], fn.tempname())
end)
end)

View File

@ -41,6 +41,21 @@ describe('server', function()
end
end)
it('broken $XDG_RUNTIME_DIR is not fatal #30282', function()
clear {
args_rm = { '--listen' },
env = { NVIM_LOG_FILE = testlog, XDG_RUNTIME_DIR = '/non-existent-dir/subdir//' },
}
if is_os('win') then
-- Windows pipes have a special namespace and thus aren't decided by $XDG_RUNTIME_DIR.
matches('nvim', api.nvim_get_vvar('servername'))
else
eq('', api.nvim_get_vvar('servername'))
t.assert_log('Failed to start server%: no such file or directory', testlog, 100)
end
end)
it('serverstart(), serverstop() does not set $NVIM', function()
clear()
local s = eval('serverstart()')
@ -139,7 +154,7 @@ describe('server', function()
clear_serverlist()
-- Address without slashes is a "name" which is appended to a generated path. #8519
matches([[.*[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4'))
matches([[[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4'))
clear_serverlist()
eq('Vim:Failed to start server: invalid argument', pcall_err(fn.serverstart, '127.0.0.1:65536')) -- invalid port
@ -175,56 +190,79 @@ describe('server', function()
end)
describe('startup --listen', function()
-- Tests Nvim output when failing to start, with and without "--headless".
-- TODO(justinmk): clear() should have a way to get stdout if Nvim fails to start.
local function _test(args, env, expected)
local function run(cmd)
return n.exec_lua(function(cmd_, env_)
return vim
.system(cmd_, {
text = true,
env = vim.tbl_extend(
'force',
-- Avoid noise in the logs; we expect failures for these tests.
{ NVIM_LOG_FILE = testlog },
env_ or {}
),
})
:wait()
end, cmd, env) --[[@as vim.SystemCompleted]]
end
local cmd = vim.list_extend({ n.nvim_prog, '+qall!', '--headless' }, args)
local r = run(cmd)
eq(1, r.code)
matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' '))
if is_os('win') then
return -- On Windows, output without --headless is garbage.
end
table.remove(cmd, 3) -- Remove '--headless'.
assert(not vim.tbl_contains(cmd, '--headless'))
r = run(cmd)
eq(1, r.code)
matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' '))
end
it('validates', function()
clear { env = { NVIM_LOG_FILE = testlog } }
-- Tests args with and without "--headless".
local function _test(args, expected)
local function run(cmd)
return n.exec_lua(function(cmd_)
return vim
.system(cmd_, {
text = true,
env = {
-- Avoid noise in the logs; we expect failures for these tests.
NVIM_LOG_FILE = testlog,
},
})
:wait()
end, cmd) --[[@as vim.SystemCompleted]]
end
local cmd = vim.list_extend({ n.nvim_prog, '+qall!', '--headless' }, args)
local r = run(cmd)
eq(1, r.code)
matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' '))
if is_os('win') then
return -- On Windows, output without --headless is garbage.
end
table.remove(cmd, 3) -- Remove '--headless'.
assert(not vim.tbl_contains(cmd, '--headless'))
r = run(cmd)
eq(1, r.code)
matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' '))
end
local in_use = n.eval('v:servername') ---@type string Address already used by another server.
t.assert_nolog('Failed to start server', testlog, 100)
t.assert_nolog('Host lookup failed', testlog, 100)
_test({ '--listen' }, 'nvim.*: Argument missing after: "%-%-listen"')
_test({ '--listen2' }, 'nvim.*: Garbage after option argument: "%-%-listen2"')
_test({ '--listen', n.eval('v:servername') }, 'nvim.*: Failed to %-%-listen: ".* already .*"')
_test({ '--listen', '/' }, 'nvim.*: Failed to %-%-listen: ".*"')
_test({ '--listen' }, nil, 'nvim.*: Argument missing after: "%-%-listen"')
_test({ '--listen2' }, nil, 'nvim.*: Garbage after option argument: "%-%-listen2"')
_test(
{ '--listen', in_use },
nil,
('nvim.*: Failed to %%-%%-listen: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use))
)
_test({ '--listen', '/' }, nil, 'nvim.*: Failed to %-%-listen: [^:]+: "/"')
_test(
{ '--listen', 'https://example.com' },
('nvim.*: Failed to %%-%%-listen: "%s"'):format(
nil,
('nvim.*: Failed to %%-%%-listen: %s: "https://example.com"'):format(
is_os('mac') and 'unknown node or service' or 'service not available for socket type'
)
)
t.assert_log('Failed to start server', testlog, 100)
t.assert_log('Host lookup failed', testlog, 100)
_test(
{},
{ NVIM_LISTEN_ADDRESS = in_use },
('nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use))
)
_test({}, { NVIM_LISTEN_ADDRESS = '/' }, 'nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+: "/"')
_test(
{},
{ NVIM_LISTEN_ADDRESS = 'https://example.com' },
('nvim.*: Failed $NVIM_LISTEN_ADDRESS: %s: "https://example.com"'):format(
is_os('mac') and 'unknown node or service' or 'service not available for socket type'
)
)
end)
it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function()
@ -235,6 +273,6 @@ describe('startup --listen', function()
-- Address without slashes is a "name" which is appended to a generated path. #8519
clear({ args = { '--listen', 'test-name' } })
matches([[.*[/\\]test%-name[^/\\]*]], api.nvim_get_vvar('servername'))
matches([[[/\\]test%-name[^/\\]*]], api.nvim_get_vvar('servername'))
end)
end)

View File

@ -27,7 +27,6 @@ local sleep = vim.uv.sleep
local startswith = vim.startswith
local write_file = t.write_file
local api = n.api
local alter_slashes = n.alter_slashes
local is_os = t.is_os
local dedent = t.dedent
local tbl_map = vim.tbl_map
@ -40,22 +39,15 @@ local testlog = 'Xtest-startupspec-log'
describe('startup', function()
it('--clean', function()
clear()
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) ~= nil
matches(
vim.pesc(t.fix_slashes(fn.stdpath('config'))),
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
clear('--clean')
ok(
string.find(
alter_slashes(api.nvim_get_option_value('runtimepath', {})),
fn.stdpath('config'),
1,
true
) == nil
not t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
:match(vim.pesc(t.fix_slashes(fn.stdpath('config'))))
)
end)

View File

@ -170,7 +170,7 @@ describe(':mksession', function()
skip(is_os('win'), 'causes rmdir() to fail')
local cwd_dir = fn.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes.
cwd_dir = t.fix_slashes(cwd_dir) -- :mksession always uses unix slashes.
local session_path = cwd_dir .. '/' .. session_file
command('cd ' .. tab_dir)

View File

@ -217,7 +217,7 @@ describe('URI methods', function()
]],
file
)
local expected_uri = 'file:///' .. file:gsub('\\', '/')
local expected_uri = 'file:///' .. t.fix_slashes(file)
eq(expected_uri, exec_lua(test_case))
os.remove(file)
end)

View File

@ -1071,42 +1071,28 @@ describe('lua stdlib', function()
]])
)
-- Fix github issue #23654
ok(exec_lua([[
local a = { sub = { [1] = 'a' } }
local b = { sub = { b = 'a' } }
local a = { sub = { 'a', 'b' } }
local b = { sub = { 'b', 'c' } }
local c = vim.tbl_deep_extend('force', a, b)
return vim.deep_equal(c, { sub = { [1] = 'a', b = 'a' } })
return vim.deep_equal(c, { sub = { 'b', 'c' } })
]]))
matches(
'invalid "behavior": nil',
pcall_err(
exec_lua,
[[
return vim.tbl_deep_extend()
]]
)
)
matches('invalid "behavior": nil', pcall_err(exec_lua, [[return vim.tbl_deep_extend()]]))
matches(
'wrong number of arguments %(given 1, expected at least 3%)',
pcall_err(
exec_lua,
[[
return vim.tbl_deep_extend("keep")
]]
)
pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep")]])
)
matches(
'wrong number of arguments %(given 2, expected at least 3%)',
pcall_err(
exec_lua,
[[
return vim.tbl_deep_extend("keep", {})
]]
)
pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep", {})]])
)
matches(
'after the second argument%: expected table, got number',
pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep", {}, 42)]])
)
end)

View File

@ -22,7 +22,7 @@ describe("'autochdir'", function()
end)
it('is not overwritten by getwinvar() call #17609', function()
local curdir = vim.uv.cwd():gsub('\\', '/')
local curdir = t.fix_slashes(vim.uv.cwd())
local dir_a = curdir .. '/Xtest-functional-options-autochdir.dir_a'
local dir_b = curdir .. '/Xtest-functional-options-autochdir.dir_b'
mkdir(dir_a)

View File

@ -23,7 +23,6 @@ local insert = n.insert
local neq = t.neq
local mkdir = t.mkdir
local rmdir = n.rmdir
local alter_slashes = n.alter_slashes
local tbl_contains = vim.tbl_contains
local expect_exit = n.expect_exit
local check_close = n.check_close
@ -262,7 +261,7 @@ describe('startup defaults', function()
NVIM_LOG_FILE = '', -- Empty is invalid.
},
})
eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE')))
end)
it('defaults to stdpath("log")/log if invalid', function()
@ -273,7 +272,7 @@ describe('startup defaults', function()
NVIM_LOG_FILE = '.', -- Any directory is invalid.
},
})
eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE')))
-- Avoid "failed to open $NVIM_LOG_FILE" noise in test output.
expect_exit(command, 'qall!')
end)
@ -383,69 +382,69 @@ describe('XDG defaults', function()
eq(
(
(
t.fix_slashes(
root_path
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
):gsub('\\', '/')
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
command('set runtimepath&')
command('set backupdir&')
@ -454,85 +453,85 @@ describe('XDG defaults', function()
command('set viewdir&')
eq(
(
(
t.fix_slashes(
root_path
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
):gsub('\\', '/')
.. ('/x'):rep(4096)
.. '/nvim'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim'
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim'
.. (',' .. root_path .. '/c/nvim')
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site'
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site'
.. (',' .. root_path .. '/C/nvim/site')
.. ','
.. vimruntime
.. ','
.. libdir
.. (',' .. root_path .. '/C/nvim/site/after')
.. ','
.. root_path
.. ('/B'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/A'):rep(2048)
.. '/nvim/site/after'
.. ','
.. root_path
.. ('/X'):rep(4096)
.. '/'
.. data_dir
.. '/site/after'
.. (',' .. root_path .. '/c/nvim/after')
.. ','
.. root_path
.. ('/b'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/a'):rep(2048)
.. '/nvim/after'
.. ','
.. root_path
.. ('/x'):rep(4096)
.. '/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
'.,' .. root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/backup//',
(api.nvim_get_option_value('backupdir', {}):gsub('\\', '/'))
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//',
(api.nvim_get_option_value('directory', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//',
(api.nvim_get_option_value('undodir', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//',
(api.nvim_get_option_value('viewdir', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
end)
end)
@ -574,26 +573,26 @@ describe('XDG defaults', function()
local vimruntime, libdir = vimruntime_and_libdir()
eq(
(
(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/')
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
)
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
command('set runtimepath&')
command('set backupdir&')
@ -602,8 +601,47 @@ describe('XDG defaults', function()
command('set viewdir&')
eq(
(
(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
)
),
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
command('set all&')
eq(
t.fix_slashes(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
@ -619,63 +657,24 @@ describe('XDG defaults', function()
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/')
),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
api.nvim_get_option_value('backupdir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('backupdir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
api.nvim_get_option_value('directory', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('directory', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
api.nvim_get_option_value('undodir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('undodir', {}))
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
api.nvim_get_option_value('viewdir', {}):gsub('\\', '/')
)
command('set all&')
eq(
(
'$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ','
.. vimruntime
.. ','
.. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/'
.. data_dir
.. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/'),
(api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/')
)
eq(
('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
api.nvim_get_option_value('backupdir', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
api.nvim_get_option_value('directory', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
api.nvim_get_option_value('undodir', {}):gsub('\\', '/')
)
eq(
('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
api.nvim_get_option_value('viewdir', {}):gsub('\\', '/')
t.fix_slashes(api.nvim_get_option_value('viewdir', {}))
)
eq(nil, (fn.tempname()):match('XDG_RUNTIME_DIR'))
end)
@ -915,7 +914,7 @@ describe('stdpath()', function()
assert_alive() -- Check for crash. #8393
end)
it('supports $NVIM_APPNAME', function()
it('$NVIM_APPNAME', function()
local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106)
clear({ env = { NVIM_APPNAME = appname, NVIM_LOG_FILE = testlog } })
eq(appname, fn.fnamemodify(fn.stdpath('config'), ':t'))
@ -939,7 +938,7 @@ describe('stdpath()', function()
local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } })
return vim.fn.jobwait({ child }, %d)[1]
]],
alter_slashes(testAppname),
testAppname,
3000
)
eq(expected_exitcode, exec_lua(lua_code))
@ -957,24 +956,43 @@ describe('stdpath()', function()
test_appname('a/b\\c', 0)
end)
it('$NVIM_APPNAME relative path', function()
local tmpdir = t.tmpname(false)
t.mkdir(tmpdir)
clear({
args_rm = { '--listen' },
env = {
NVIM_APPNAME = 'relative/appname',
NVIM_LOG_FILE = testlog,
TMPDIR = tmpdir,
},
})
t.matches(vim.pesc(tmpdir), t.fix_slashes(fn.tempname()))
t.assert_nolog('tempdir', testlog, 100)
t.assert_nolog('TMPDIR', testlog, 100)
t.matches([=[[/\\]relative%-appname.[^/\\]+]=], api.nvim_get_vvar('servername'))
end)
describe('returns a String', function()
describe('with "config"', function()
it('knows XDG_CONFIG_HOME', function()
clear({
env = {
XDG_CONFIG_HOME = alter_slashes('/home/docwhat/.config'),
XDG_CONFIG_HOME = '/home/docwhat/.config',
},
})
eq(alter_slashes('/home/docwhat/.config/nvim'), fn.stdpath('config'))
eq('/home/docwhat/.config/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_CONFIG_HOME = alter_slashes('/home/original'),
XDG_CONFIG_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/nvim'), fn.stdpath('config'))
command("let $XDG_CONFIG_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/nvim'), fn.stdpath('config'))
eq('/home/original/nvim', t.fix_slashes(fn.stdpath('config')))
command("let $XDG_CONFIG_HOME='/home/new'")
eq('/home/new/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it("doesn't expand $VARIABLES", function()
@ -984,32 +1002,32 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('config'))
eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('config')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_CONFIG_HOME = alter_slashes('~/frobnitz'),
XDG_CONFIG_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('config'))
eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('config')))
end)
end)
describe('with "data"', function()
it('knows XDG_DATA_HOME', function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('/home/docwhat/.local'),
XDG_DATA_HOME = '/home/docwhat/.local',
} })
eq(alter_slashes('/home/docwhat/.local/' .. datadir), fn.stdpath('data'))
eq('/home/docwhat/.local/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('/home/original'),
XDG_DATA_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/' .. datadir), fn.stdpath('data'))
command("let $XDG_DATA_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/' .. datadir), fn.stdpath('data'))
eq('/home/original/' .. datadir, t.fix_slashes(fn.stdpath('data')))
command("let $XDG_DATA_HOME='/home/new'")
eq('/home/new/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it("doesn't expand $VARIABLES", function()
@ -1019,14 +1037,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/' .. datadir), fn.stdpath('data'))
eq('$VARIABLES/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_DATA_HOME = alter_slashes('~/frobnitz'),
XDG_DATA_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/' .. datadir), fn.stdpath('data'))
eq('~/frobnitz/' .. datadir, t.fix_slashes(fn.stdpath('data')))
end)
end)
@ -1034,19 +1052,19 @@ describe('stdpath()', function()
it('knows XDG_STATE_HOME', function()
clear({
env = {
XDG_STATE_HOME = alter_slashes('/home/docwhat/.local'),
XDG_STATE_HOME = '/home/docwhat/.local',
},
})
eq(alter_slashes('/home/docwhat/.local/' .. statedir), fn.stdpath('state'))
eq('/home/docwhat/.local/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_STATE_HOME = alter_slashes('/home/original'),
XDG_STATE_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/' .. statedir), fn.stdpath('state'))
command("let $XDG_STATE_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/' .. statedir), fn.stdpath('state'))
eq('/home/original/' .. statedir, t.fix_slashes(fn.stdpath('state')))
command("let $XDG_STATE_HOME='" .. '/home/new' .. "'")
eq('/home/new/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it("doesn't expand $VARIABLES", function()
@ -1056,14 +1074,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/' .. statedir), fn.stdpath('state'))
eq('$VARIABLES/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_STATE_HOME = alter_slashes('~/frobnitz'),
XDG_STATE_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/' .. statedir), fn.stdpath('state'))
eq('~/frobnitz/' .. statedir, t.fix_slashes(fn.stdpath('state')))
end)
end)
@ -1071,19 +1089,19 @@ describe('stdpath()', function()
it('knows XDG_CACHE_HOME', function()
clear({
env = {
XDG_CACHE_HOME = alter_slashes('/home/docwhat/.cache'),
XDG_CACHE_HOME = '/home/docwhat/.cache',
},
})
eq(alter_slashes('/home/docwhat/.cache/nvim'), fn.stdpath('cache'))
eq('/home/docwhat/.cache/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it('handles changes during runtime', function()
clear({ env = {
XDG_CACHE_HOME = alter_slashes('/home/original'),
XDG_CACHE_HOME = '/home/original',
} })
eq(alter_slashes('/home/original/nvim'), fn.stdpath('cache'))
command("let $XDG_CACHE_HOME='" .. alter_slashes('/home/new') .. "'")
eq(alter_slashes('/home/new/nvim'), fn.stdpath('cache'))
eq('/home/original/nvim', t.fix_slashes(fn.stdpath('cache')))
command("let $XDG_CACHE_HOME='" .. '/home/new' .. "'")
eq('/home/new/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it("doesn't expand $VARIABLES", function()
@ -1093,14 +1111,14 @@ describe('stdpath()', function()
VARIABLES = 'this-should-not-happen',
},
})
eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('cache'))
eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
it("doesn't expand ~/", function()
clear({ env = {
XDG_CACHE_HOME = alter_slashes('~/frobnitz'),
XDG_CACHE_HOME = '~/frobnitz',
} })
eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('cache'))
eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('cache')))
end)
end)
end)
@ -1114,6 +1132,7 @@ describe('stdpath()', function()
HOMEDRIVE = 'C:',
HOMEPATH = '\\Users\\docwhat',
LOCALAPPDATA = 'C:\\Users\\docwhat\\AppData\\Local',
NVIM_LOG_FILE = testlog,
TEMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
TMPDIR = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
TMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp',
@ -1124,6 +1143,7 @@ describe('stdpath()', function()
HOMEDRIVE = 'HOMEDRIVE-should-be-ignored',
HOMEPATH = 'HOMEPATH-should-be-ignored',
LOCALAPPDATA = 'LOCALAPPDATA-should-be-ignored',
NVIM_LOG_FILE = testlog,
TEMP = 'TEMP-should-be-ignored',
TMPDIR = 'TMPDIR-should-be-ignored',
TMP = 'TMP-should-be-ignored',
@ -1147,12 +1167,18 @@ describe('stdpath()', function()
describe(msg, function()
it('set via system', function()
set_paths_via_system(env_var_name, paths)
eq(expected_paths, fn.stdpath(stdpath_arg))
eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg)))
if not is_os('win') then
assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100)
end
end)
it('set at runtime', function()
set_paths_at_runtime(env_var_name, paths)
eq(expected_paths, fn.stdpath(stdpath_arg))
eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg)))
if not is_os('win') then
assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100)
end
end)
end)
end
@ -1163,10 +1189,10 @@ describe('stdpath()', function()
'config_dirs',
'XDG_CONFIG_DIRS',
{
alter_slashes('/home/docwhat/.config'),
t.fix_slashes('/home/docwhat/.config'),
},
{
alter_slashes('/home/docwhat/.config/nvim'),
t.fix_slashes('/home/docwhat/.config/nvim'),
}
)
@ -1175,12 +1201,12 @@ describe('stdpath()', function()
'config_dirs',
'XDG_CONFIG_DIRS',
{
alter_slashes('/home/docwhat/.config'),
alter_slashes('/etc/config'),
t.fix_slashes('/home/docwhat/.config'),
t.fix_slashes('/etc/config'),
},
{
alter_slashes('/home/docwhat/.config/nvim'),
alter_slashes('/etc/config/nvim'),
t.fix_slashes('/home/docwhat/.config/nvim'),
t.fix_slashes('/etc/config/nvim'),
}
)
@ -1190,25 +1216,25 @@ describe('stdpath()', function()
'XDG_CONFIG_DIRS',
{ '$HOME', '$TMP' },
{
alter_slashes('$HOME/nvim'),
alter_slashes('$TMP/nvim'),
t.fix_slashes('$HOME/nvim'),
t.fix_slashes('$TMP/nvim'),
}
)
behaves_like_dir_list_env("doesn't expand ~/", 'config_dirs', 'XDG_CONFIG_DIRS', {
alter_slashes('~/.oldconfig'),
alter_slashes('~/.olderconfig'),
t.fix_slashes('~/.oldconfig'),
t.fix_slashes('~/.olderconfig'),
}, {
alter_slashes('~/.oldconfig/nvim'),
alter_slashes('~/.olderconfig/nvim'),
t.fix_slashes('~/.oldconfig/nvim'),
t.fix_slashes('~/.olderconfig/nvim'),
})
end)
describe('with "data_dirs"', function()
behaves_like_dir_list_env('knows XDG_DATA_DIRS with one path', 'data_dirs', 'XDG_DATA_DIRS', {
alter_slashes('/home/docwhat/.data'),
t.fix_slashes('/home/docwhat/.data'),
}, {
alter_slashes('/home/docwhat/.data/nvim'),
t.fix_slashes('/home/docwhat/.data/nvim'),
})
behaves_like_dir_list_env(
@ -1216,12 +1242,12 @@ describe('stdpath()', function()
'data_dirs',
'XDG_DATA_DIRS',
{
alter_slashes('/home/docwhat/.data'),
alter_slashes('/etc/local'),
t.fix_slashes('/home/docwhat/.data'),
t.fix_slashes('/etc/local'),
},
{
alter_slashes('/home/docwhat/.data/nvim'),
alter_slashes('/etc/local/nvim'),
t.fix_slashes('/home/docwhat/.data/nvim'),
t.fix_slashes('/etc/local/nvim'),
}
)
@ -1231,17 +1257,17 @@ describe('stdpath()', function()
'XDG_DATA_DIRS',
{ '$HOME', '$TMP' },
{
alter_slashes('$HOME/nvim'),
alter_slashes('$TMP/nvim'),
t.fix_slashes('$HOME/nvim'),
t.fix_slashes('$TMP/nvim'),
}
)
behaves_like_dir_list_env("doesn't expand ~/", 'data_dirs', 'XDG_DATA_DIRS', {
alter_slashes('~/.oldconfig'),
alter_slashes('~/.olderconfig'),
t.fix_slashes('~/.oldconfig'),
t.fix_slashes('~/.olderconfig'),
}, {
alter_slashes('~/.oldconfig/nvim'),
alter_slashes('~/.olderconfig/nvim'),
t.fix_slashes('~/.oldconfig/nvim'),
t.fix_slashes('~/.olderconfig/nvim'),
})
end)
end)

View File

@ -3607,21 +3607,21 @@ describe('LSP', function()
range = {
['end'] = {
character = 8,
line = 9,
line = 3,
},
start = {
character = 6,
line = 9,
line = 3,
},
},
selectionRange = {
['end'] = {
character = 8,
line = 9,
line = 3,
},
start = {
character = 6,
line = 9,
line = 3,
},
},
uri = 'file:///home/jiangyinzuo/hello.cpp',
@ -3651,21 +3651,21 @@ describe('LSP', function()
range = {
['end'] = {
character = 8,
line = 8,
line = 2,
},
start = {
character = 6,
line = 8,
line = 2,
},
},
selectionRange = {
['end'] = {
character = 8,
line = 8,
line = 2,
},
start = {
character = 6,
line = 8,
line = 2,
},
},
uri = 'file:///home/jiangyinzuo/hello.cpp',
@ -3679,7 +3679,15 @@ describe('LSP', function()
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes']
handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
'class B : public A{};',
'class C : public B{};',
'class D1 : public C{};',
'class D2 : public C{};',
'class E : public D1, D2 {};',
})
handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr })
return vim.fn.getqflist()
end)
@ -3689,7 +3697,7 @@ describe('LSP', function()
col = 7,
end_col = 0,
end_lnum = 0,
lnum = 10,
lnum = 4,
module = '',
nr = 0,
pattern = '',
@ -3703,7 +3711,7 @@ describe('LSP', function()
col = 7,
end_col = 0,
end_lnum = 0,
lnum = 9,
lnum = 3,
module = '',
nr = 0,
pattern = '',
@ -3763,7 +3771,15 @@ describe('LSP', function()
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes']
handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
'package mylist;',
'',
'public class MyList {',
' static class Inner extends MyList{}',
'~}',
})
handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr })
return vim.fn.getqflist()
end)
@ -3840,21 +3856,21 @@ describe('LSP', function()
range = {
['end'] = {
character = 8,
line = 9,
line = 3,
},
start = {
character = 6,
line = 9,
line = 3,
},
},
selectionRange = {
['end'] = {
character = 8,
line = 9,
line = 3,
},
start = {
character = 6,
line = 9,
line = 3,
},
},
uri = 'file:///home/jiangyinzuo/hello.cpp',
@ -3884,21 +3900,21 @@ describe('LSP', function()
range = {
['end'] = {
character = 8,
line = 8,
line = 2,
},
start = {
character = 6,
line = 8,
line = 2,
},
},
selectionRange = {
['end'] = {
character = 8,
line = 8,
line = 2,
},
start = {
character = 6,
line = 8,
line = 2,
},
},
uri = 'file:///home/jiangyinzuo/hello.cpp',
@ -3912,7 +3928,16 @@ describe('LSP', function()
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes']
handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
'class B : public A{};',
'class C : public B{};',
'class D1 : public C{};',
'class D2 : public C{};',
'class E : public D1, D2 {};',
})
handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr })
return vim.fn.getqflist()
end)
@ -3922,7 +3947,7 @@ describe('LSP', function()
col = 7,
end_col = 0,
end_lnum = 0,
lnum = 10,
lnum = 4,
module = '',
nr = 0,
pattern = '',
@ -3936,7 +3961,7 @@ describe('LSP', function()
col = 7,
end_col = 0,
end_lnum = 0,
lnum = 9,
lnum = 3,
module = '',
nr = 0,
pattern = '',
@ -3996,7 +4021,15 @@ describe('LSP', function()
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes']
handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
'package mylist;',
'',
'public class MyList {',
' static class Inner extends MyList{}',
'~}',
})
handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr })
return vim.fn.getqflist()
end)

View File

@ -136,6 +136,50 @@ local function run_tohtml_and_assert(screen, func)
screen:expect({ grid = expected.grid, attr_ids = expected.attr_ids })
end
---@param guifont boolean
local function test_generates_html(guifont, expect_font)
insert([[line]])
exec('set termguicolors')
local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui')
local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui')
local tmpfile = t.tmpname()
exec_lua(
[[
local guifont, outfile = ...
local html = (guifont
and require('tohtml').tohtml(0,{title="title"})
or require('tohtml').tohtml(0,{title="title",font={ "dumyfont","anotherfont" }}))
vim.fn.writefile(html, outfile)
vim.cmd.split(outfile)
]],
guifont,
tmpfile
)
local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf())
eq({
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="UTF-8">',
'<title>title</title>',
('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')),
'<style>',
('* {font-family: %s,monospace}'):format(expect_font),
('body {background-color: %s; color: %s}'):format(bg, fg),
'</style>',
'</head>',
'<body style="display: flex">',
'<pre>',
'line',
'',
'</pre>',
'</body>',
'</html>',
}, fn.readfile(out_file))
end
describe(':TOhtml', function()
--- @type test.functional.ui.screen
local screen
@ -146,41 +190,16 @@ describe(':TOhtml', function()
exec('colorscheme default')
end)
it('expected internal html generated', function()
insert([[line]])
exec('set termguicolors')
local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui')
local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui')
exec_lua [[
local outfile = vim.fn.tempname() .. '.html'
local html = require('tohtml').tohtml(0,{title="title",font="dumyfont"})
vim.fn.writefile(html, outfile)
vim.cmd.split(outfile)
]]
local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf())
eq({
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="UTF-8">',
'<title>title</title>',
('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')),
'<style>',
'* {font-family: dumyfont,monospace}',
('body {background-color: %s; color: %s}'):format(bg, fg),
'</style>',
'</head>',
'<body style="display: flex">',
'<pre>',
'line',
'',
'</pre>',
'</body>',
'</html>',
}, fn.readfile(out_file))
it('generates html with given font', function()
test_generates_html(false, '"dumyfont","anotherfont"')
end)
it('expected internal html generated from range', function()
it("generates html, respects 'guifont'", function()
exec_lua [[vim.o.guifont='Font,Escape\\,comma, Ignore space after comma']]
test_generates_html(true, '"Font","Escape,comma","Ignore space after comma"')
end)
it('generates html from range', function()
insert([[
line1
line2
@ -218,7 +237,7 @@ describe(':TOhtml', function()
}, fn.readfile(out_file))
end)
it('highlight attributes generated', function()
it('generates highlight attributes', function()
--Make sure to uncomment the attribute in `html_syntax_match()`
exec('hi LINE guisp=#00ff00 gui=' .. table.concat({
'bold',

View File

@ -342,7 +342,7 @@ describe(':terminal buffer', function()
command('wincmd p')
-- cwd will be inserted in a file URI, which cannot contain backs
local cwd = fn.getcwd():gsub('\\', '/')
local cwd = t.fix_slashes(fn.getcwd())
local parent = cwd:match('^(.+/)')
local expected = '\027]7;file://host' .. parent
api.nvim_chan_send(term, string.format('%s\027\\', expected))

View File

@ -14,8 +14,7 @@ local is_os = t.is_os
local ok = t.ok
local sleep = uv.sleep
--- This module uses functions from the context of the test session, i.e. in the context of the
--- nvim being tests.
--- Functions executing in the current nvim session/process being tested.
local M = {}
local runtime_set = 'set runtimepath^=./build/lib/nvim/'
@ -903,26 +902,6 @@ function M.missing_provider(provider)
assert(false, 'Unknown provider: ' .. provider)
end
--- @param obj string|table
--- @return any
function M.alter_slashes(obj)
if not is_os('win') then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('/', '\\')
return ret
elseif type(obj) == 'table' then
--- @cast obj table<any,any>
local ret = {} --- @type table<any,any>
for k, v in pairs(obj) do
ret[k] = M.alter_slashes(v)
end
return ret
end
assert(false, 'expected string or table of strings, got ' .. type(obj))
end
local load_factor = 1
if t.is_ci() then
-- Compute load factor only once (but outside of any tests).

View File

@ -1344,6 +1344,53 @@ describe('float window', function()
|
]])
end
--
-- floating windows inherit NormalFloat from global-ns.
--
command('fclose')
command('hi NormalFloat guibg=LightRed')
api.nvim_open_win(0, false, { relative = 'win', row = 3, col = 3, width = 12, height = 3, style = 'minimal' })
api.nvim_set_hl_ns(api.nvim_create_namespace('test1'))
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
{14: 1 }^x |
{14: 2 }y |
{14: 3 } |
{0:~ }|*3
## grid 3
|
## grid 5
{22:x }|
{22:y }|
{22: }|
]], float_pos={
[5] = {1002, "NW", 2, 3, 3, true, 50};
}, win_viewport={
[2] = {win = 1000, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
[5] = {win = 1002, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 },
[5] = { bottom = 0, left = 0, right = 0, top = 0, win = 1002 }
}})
else
screen:expect({
grid = [[
{14: 1 }^x |
{14: 2 }y |
{14: 3 } |
{0:~ }{22:x }{0: }|
{0:~ }{22:y }{0: }|
{0:~ }{22: }{0: }|
|
]]
})
end
end)
it("can use 'minimal' style", function()

View File

@ -7,11 +7,10 @@ local fnamemodify = n.fn.fnamemodify
local getcwd = n.fn.getcwd
local command = n.command
local write_file = t.write_file
local alter_slashes = n.alter_slashes
local is_os = t.is_os
local function eq_slashconvert(expected, got)
eq(alter_slashes(expected), alter_slashes(got))
eq(t.fix_slashes(expected), t.fix_slashes(got))
end
describe('fnamemodify()', function()

View File

@ -16,7 +16,7 @@ local function shell_quote(str)
return str
end
--- This module uses functions from the context of the test runner.
--- Functions executing in the context of the test runner (not the current nvim test session).
--- @class test.testutil
local M = {
paths = Paths,
@ -42,6 +42,29 @@ function M.isdir(path)
return stat.type == 'directory'
end
--- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all
--- string values in a table (recursively).
---
--- @param obj string|table
--- @return any
function M.fix_slashes(obj)
if not M.is_os('win') then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('\\', '/')
return ret
elseif type(obj) == 'table' then
--- @cast obj table<any,any>
local ret = {} --- @type table<any,any>
for k, v in pairs(obj) do
ret[k] = M.fix_slashes(v)
end
return ret
end
assert(false, 'expected string or table of strings, got ' .. type(obj))
end
--- @param ... string|string[]
--- @return string
function M.argss_to_cmd(...)