Compare commits

..

23 Commits

Author SHA1 Message Date
LosFarmosCTL
a4680a08c3 fix(runtime): add remaining missing commentstrings 2024-09-09 17:31:22 +02: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
zeertzjq
3d1110674e
vim-patch:9.1.0720: Wrong breakindentopt=list:-1 with multibyte or TABs (#30293)
Problem:  Wrong breakindentopt=list:-1 with multibyte chars or TABs in
          text matched by 'formatlistpat' (John M Devin)
Solution: Use the width of the match text (zeertzjq)

fixes: vim/vim#15634
closes: vim/vim#15635

61a6ac4d00
2024-09-07 10:50:52 +00:00
zeertzjq
738a84de09
vim-patch:9.1.0719: Resetting cell widths can make 'listchars' or 'fillchars' invalid (#30289)
Problem:  Resetting cell widths can make 'listchars' or 'fillchars'
          invalid.
Solution: Check for conflicts when resetting cell widths (zeertzjq).

closes: vim/vim#15629

66f65a46c5
2024-09-06 23:36:51 +00:00
bfredl
439d031742
Merge pull request #30236 from luukvbaal/invalid
fix(decor): revise marktree metadata for invalid marks
2024-09-06 13:13:51 +02:00
bfredl
c81cb02dd6
Merge pull request #30272 from bfredl/replace_emoji
fix(multibyte): handle backspace of wide clusters in replace mode
2024-09-06 12:08:26 +02:00
bfredl
fa99afe35e fix(multibyte): handle backspace of wide clusters in replace mode
Make utf_head_off more robust against invalid sequences
and embedded NUL chars
2024-09-06 10:22:29 +02:00
zeertzjq
9570ad24f5
vim-patch:9.1.0717: Unnecessary nextcmd NULL checks in parse_command_modifiers() (#30275)
Problem:  Unnecessary nextcmd NULL checks in parse_command_modifiers().
Solution: Remove them (zeertzjq)

Every place parse_command_modifiers() is called, nextcmd is NULL, and
after it's set to non-NULL the function returns very soon.
Even if one day nextcmd may be non-NULL, the NULL checks may still be
wrong as the correct behavior may be overriding nextcmd.

closes: vim/vim#15620

f7b8609446
2024-09-06 07:23:31 +08:00
zeertzjq
d60c753cff
vim-patch:9.1.0716: resetting setcellwidth() doesn't update the screen (#30274)
Problem:  resetting setcellwidth() doesn't update the screen
Solution: Redraw after clearing the cellwidth table (Ken Takata)

closes: vim/vim#15628

539e9b571a

Co-authored-by: Ken Takata <kentkt@csc.jp>
2024-09-06 06:52:13 +08:00
Luuk van Baal
34ded4d97b fix(decor): exclude invalid marks from meta total
Problem:  Marktree meta count still includes invalidated marks, making
          guards that check the meta total ineffective.
Solution: Revise marktree metadata when in/revalidating a mark.
2024-09-04 15:13:12 +02:00
59 changed files with 1008 additions and 771 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

@ -214,9 +214,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

@ -1129,9 +1129,9 @@ A jump table for the options with a short description can be found at |Q_op|.
list:{n} Adds an additional indent for lines that match a
numbered or bulleted list (using the
'formatlistpat' setting).
list:-1 Uses the length of a match with 'formatlistpat'
for indentation.
(default: 0)
list:-1 Uses the width of a match with 'formatlistpat' for
indentation.
column:{n} Indent at column {n}. Will overrule the other
sub-options. Note: an additional indent may be
added for the 'showbreak' setting.

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*

View File

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

View File

@ -11,4 +11,4 @@ if vim.fn.isdirectory('/usr/include') == 1 then
]])
end
vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | setl path< commentstring< define< include<'
vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | setl path<'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1 @@
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

@ -558,9 +558,9 @@ vim.wo.bri = vim.wo.breakindent
--- list:{n} Adds an additional indent for lines that match a
--- numbered or bulleted list (using the
--- 'formatlistpat' setting).
--- list:-1 Uses the length of a match with 'formatlistpat'
--- for indentation.
--- (default: 0)
--- list:-1 Uses the width of a match with 'formatlistpat' for
--- indentation.
--- column:{n} Indent at column {n}. Will overrule the other
--- sub-options. Note: an additional indent may be
--- added for the 'showbreak' setting.

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

@ -147,6 +147,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
@ -166,7 +172,6 @@ function M._str_byteindex_enc(line, index, encoding)
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.
---
@ -334,12 +339,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 +436,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 +1796,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,

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

@ -28,6 +28,7 @@
#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"

View File

@ -756,10 +756,8 @@ void ins_char_bytes(char *buf, size_t charlen)
// put back when BS is used. The bytes of a multi-byte character are
// done the other way around, so that the first byte is popped off
// first (it tells the byte length of the character).
replace_push(NUL);
for (size_t i = 0; i < oldlen; i++) {
i += (size_t)replace_push_mb(oldp + col + i) - 1;
}
replace_push_nul();
replace_push(oldp + col, oldlen);
}
char *newp = xmalloc(linelen + newlen - oldlen);
@ -1137,12 +1135,10 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// on the line onto the replace stack. We'll push any other characters
// that might be replaced at the start of the next line (due to
// autoindent etc) a bit later.
replace_push(NUL); // Call twice because BS over NL expects it
replace_push(NUL);
replace_push_nul(); // Call twice because BS over NL expects it
replace_push_nul();
p = saved_line + curwin->w_cursor.col;
while (*p != NUL) {
p += replace_push_mb(p);
}
replace_push(p, strlen(p));
saved_line[curwin->w_cursor.col] = NUL;
}
@ -1691,13 +1687,13 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// stack, preceded by a NUL, so they can be put back when a BS is
// entered.
if (REPLACE_NORMAL(State)) {
replace_push(NUL); // end of extra blanks
replace_push_nul(); // end of extra blanks
}
if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
while ((*p_extra == ' ' || *p_extra == '\t')
&& !utf_iscomposing_first(utf_ptr2char(p_extra + 1))) {
if (REPLACE_NORMAL(State)) {
replace_push(*p_extra);
replace_push(p_extra, 1); // always ascii, len = 1
}
p_extra++;
less_cols_off++;
@ -1794,7 +1790,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// must be a NUL on the replace stack, for when it is deleted with BS
if (REPLACE_NORMAL(State)) {
for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
replace_push(NUL);
replace_push_nul();
}
}
newcol += curwin->w_cursor.col;
@ -1808,7 +1804,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// must be a NUL on the replace stack, for when it is deleted with BS.
if (REPLACE_NORMAL(State)) {
while (lead_len-- > 0) {
replace_push(NUL);
replace_push_nul();
}
}

View File

@ -136,6 +136,8 @@ static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo
static linenr_T o_lnum = 0;
static kvec_t(char) replace_stack = KV_INITIAL_VALUE;
static void insert_enter(InsertState *s)
{
s->did_backspace = true;
@ -1618,9 +1620,8 @@ void undisplay_dollar(void)
/// type == INDENT_SET set indent to "amount"
///
/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec).
/// @param replaced replaced character, put on replace stack
/// @param call_changed_bytes call changed_bytes()
void change_indent(int type, int amount, int round, int replaced, bool call_changed_bytes)
void change_indent(int type, int amount, int round, bool call_changed_bytes)
{
int insstart_less; // reduction for Insstart.col
colnr_T orig_col = 0; // init for GCC
@ -1767,12 +1768,8 @@ void change_indent(int type, int amount, int round, int replaced, bool call_chan
replace_join(0); // remove a NUL from the replace stack
start_col--;
}
while (start_col < (int)curwin->w_cursor.col || replaced) {
replace_push(NUL);
if (replaced) {
replace_push(replaced);
replaced = NUL;
}
while (start_col < (int)curwin->w_cursor.col) {
replace_push_nul();
start_col++;
}
}
@ -2325,7 +2322,7 @@ int stop_arrow(void)
static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
{
stop_redo_ins();
replace_flush(); // abandon replace stack
kv_destroy(replace_stack); // abandon replace stack (reinitializes)
// Save the inserted text for later redo with ^@ and CTRL-A.
// Don't do it when "restart_edit" was set and nothing was inserted,
@ -2802,57 +2799,51 @@ static bool echeck_abbr(int c)
// that the NL replaced. The extra one stores the characters after the cursor
// that were deleted (always white space).
static uint8_t *replace_stack = NULL;
static ssize_t replace_stack_nr = 0; // next entry in replace stack
static ssize_t replace_stack_len = 0; // max. number of entries
/// Push character that is replaced onto the replace stack.
///
/// replace_offset is normally 0, in which case replace_push will add a new
/// character at the end of the stack. If replace_offset is not 0, that many
/// characters will be left on the stack above the newly inserted character.
///
/// @param c character that is replaced (NUL is none)
void replace_push(int c)
/// @param str character that is replaced (NUL is none)
/// @param len length of character in bytes
void replace_push(char *str, size_t len)
{
if (replace_stack_nr < replace_offset) { // nothing to do
// TODO(bfredl): replace_offset is suss af, if we don't need it, this
// function is just kv_concat() :p
if (kv_size(replace_stack) < (size_t)replace_offset) { // nothing to do
return;
}
if (replace_stack_len <= replace_stack_nr) {
replace_stack_len += 50;
replace_stack = xrealloc(replace_stack, (size_t)replace_stack_len);
}
uint8_t *p = replace_stack + replace_stack_nr - replace_offset;
kv_ensure_space(replace_stack, len);
char *p = replace_stack.items + kv_size(replace_stack) - replace_offset;
if (replace_offset) {
memmove(p + 1, p, (size_t)replace_offset);
memmove(p + len, p, (size_t)replace_offset);
}
*p = (uint8_t)c;
replace_stack_nr++;
memcpy(p, str, len);
kv_size(replace_stack) += len;
}
/// Push a character onto the replace stack. Handles a multi-byte character in
/// reverse byte order, so that the first byte is popped off first.
///
/// @return the number of bytes done (includes composing characters).
int replace_push_mb(char *p)
/// push NUL as separator between entries in the stack
void replace_push_nul(void)
{
int l = utfc_ptr2len(p);
// TODO(bfredl): stop doing this insantity and instead use utf_head_off() when popping.
// or just keep a secondary array with char byte lenghts
for (int j = l - 1; j >= 0; j--) {
replace_push(p[j]);
}
return l;
replace_push("", 1);
}
/// Pop one item from the replace stack.
/// Check top of replace stack, pop it if it was NUL
///
/// @return -1 if stack is empty, replaced character or NUL otherwise
static int replace_pop(void)
/// when a non-NUL byte is found, use mb_replace_pop_ins() to
/// pop one complete multibyte character.
///
/// @return -1 if stack is empty, last byte of char or NUL otherwise
static int replace_pop_if_nul(void)
{
return (replace_stack_nr == 0) ? -1 : (int)replace_stack[--replace_stack_nr];
int ch = (kv_size(replace_stack)) ? (uint8_t)kv_A(replace_stack, kv_size(replace_stack) - 1) : -1;
if (ch == NUL) {
kv_size(replace_stack)--;
}
return ch;
}
/// Join the top two items on the replace stack. This removes to "off"'th NUL
@ -2861,11 +2852,11 @@ static int replace_pop(void)
/// @param off offset for which NUL to remove
static void replace_join(int off)
{
for (ssize_t i = replace_stack_nr; --i >= 0;) {
if (replace_stack[i] == NUL && off-- <= 0) {
replace_stack_nr--;
memmove(replace_stack + i, replace_stack + i + 1,
(size_t)(replace_stack_nr - i));
for (ssize_t i = (ssize_t)kv_size(replace_stack); --i >= 0;) {
if (kv_A(replace_stack, i) == NUL && off-- <= 0) {
kv_size(replace_stack)--;
memmove(&kv_A(replace_stack, i), &kv_A(replace_stack, i + 1),
(kv_size(replace_stack) - (size_t)i));
return;
}
}
@ -2875,72 +2866,25 @@ static void replace_join(int off)
/// before the cursor. Can only be used in MODE_REPLACE or MODE_VREPLACE state.
static void replace_pop_ins(void)
{
int cc;
int oldState = State;
State = MODE_NORMAL; // don't want MODE_REPLACE here
while ((cc = replace_pop()) > 0) {
mb_replace_pop_ins(cc);
while ((replace_pop_if_nul()) > 0) {
mb_replace_pop_ins();
dec_cursor();
}
State = oldState;
}
// Insert bytes popped from the replace stack. "cc" is the first byte. If it
// indicates a multi-byte char, pop the other bytes too.
static void mb_replace_pop_ins(int cc)
/// Insert multibyte char popped from the replace stack.
///
/// caller must already have checked the top of the stack is not NUL!!
static void mb_replace_pop_ins(void)
{
int n;
uint8_t buf[MB_MAXBYTES + 1];
if ((n = MB_BYTE2LEN(cc)) > 1) {
buf[0] = (uint8_t)cc;
for (int i = 1; i < n; i++) {
buf[i] = (uint8_t)replace_pop();
}
ins_bytes_len((char *)buf, (size_t)n);
} else {
ins_char(cc);
}
// Handle composing chars.
while (true) {
int c = replace_pop();
if (c == -1) { // stack empty
break;
}
if ((n = MB_BYTE2LEN(c)) == 1) {
// Not a multi-byte char, put it back.
replace_push(c);
break;
}
buf[0] = (uint8_t)c;
assert(n > 1);
for (int i = 1; i < n; i++) {
buf[i] = (uint8_t)replace_pop();
}
// TODO(bfredl): by fixing replace_push_mb, upgrade to use
// the new composing algorithm
if (utf_iscomposing_legacy(utf_ptr2char((char *)buf))) {
ins_bytes_len((char *)buf, (size_t)n);
} else {
// Not a composing char, put it back.
for (int i = n - 1; i >= 0; i--) {
replace_push(buf[i]);
}
break;
}
}
}
// make the replace stack empty
// (called when exiting replace mode)
static void replace_flush(void)
{
XFREE_CLEAR(replace_stack);
replace_stack_len = 0;
replace_stack_nr = 0;
int len = utf_head_off(&kv_A(replace_stack, 0),
&kv_A(replace_stack, kv_size(replace_stack) - 1)) + 1;
kv_size(replace_stack) -= (size_t)len;
ins_bytes_len(&kv_A(replace_stack, kv_size(replace_stack)), (size_t)len);
}
// Handle doing a BS for one character.
@ -2955,7 +2899,7 @@ static void replace_do_bs(int limit_col)
colnr_T start_vcol;
const int l_State = State;
int cc = replace_pop();
int cc = replace_pop_if_nul();
if (cc > 0) {
int orig_len = 0;
int orig_vcols = 0;
@ -2969,7 +2913,6 @@ static void replace_do_bs(int limit_col)
if (l_State & VREPLACE_FLAG) {
orig_len = get_cursor_pos_len();
}
replace_push(cc);
replace_pop_ins();
if (l_State & VREPLACE_FLAG) {
@ -3628,9 +3571,9 @@ static void ins_shift(int c, int lastc)
if (lastc == '^') {
old_indent = get_indent(); // remember curr. indent
}
change_indent(INDENT_SET, 0, true, 0, true);
change_indent(INDENT_SET, 0, true, true);
} else {
change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, 0, true);
change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, true);
}
if (did_ai && *skipwhite(get_cursor_line_ptr()) != NUL) {
@ -3749,7 +3692,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// cc >= 0: NL was replaced, put original characters back
cc = -1;
if (State & REPLACE_FLAG) {
cc = replace_pop(); // returns -1 if NL was inserted
cc = replace_pop_if_nul(); // returns -1 if NL was inserted
}
// In replace mode, in the line we started replacing, we only move the
// cursor.
@ -3795,9 +3738,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// restore characters (blanks) deleted after cursor
while (cc > 0) {
colnr_T save_col = curwin->w_cursor.col;
mb_replace_pop_ins(cc);
mb_replace_pop_ins();
curwin->w_cursor.col = save_col;
cc = replace_pop();
cc = replace_pop_if_nul();
}
// restore the characters that NL replaced
replace_pop_ins();
@ -3906,7 +3849,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
} else {
ins_str(" ");
if ((State & REPLACE_FLAG)) {
replace_push(NUL);
replace_push_nul();
}
}
}
@ -4316,7 +4259,7 @@ static bool ins_tab(void)
} else {
ins_str(" ");
if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
replace_push_nul();
}
}
}
@ -4483,7 +4426,7 @@ bool ins_eol(int c)
// character under the cursor. Only push a NUL on the replace stack,
// nothing to put back when the NL is deleted.
if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) {
replace_push(NUL);
replace_push_nul();
}
// In MODE_VREPLACE state, a NL replaces the rest of the line, and starts
@ -4684,7 +4627,7 @@ static void ins_try_si(int c)
i = get_indent();
curwin->w_cursor = old_pos;
if (State & VREPLACE_FLAG) {
change_indent(INDENT_SET, i, false, NUL, true);
change_indent(INDENT_SET, i, false, true);
} else {
set_indent(i, SIN_CHANGED);
}

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

@ -2500,15 +2500,13 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
// ignore comment and empty lines
if (*eap->cmd == '"') {
// a comment ends at a NL
if (eap->nextcmd == NULL) {
eap->nextcmd = vim_strchr(eap->cmd, '\n');
if (eap->nextcmd != NULL) {
eap->nextcmd++;
}
eap->nextcmd = vim_strchr(eap->cmd, '\n');
if (eap->nextcmd != NULL) {
eap->nextcmd++;
}
return FAIL;
}
if (eap->nextcmd == NULL && *eap->cmd == '\n') {
if (*eap->cmd == '\n') {
eap->nextcmd = eap->cmd + 1;
return FAIL;
}

View File

@ -70,24 +70,19 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
extmark_del_id(buf, ns_id, id);
} else {
assert(marktree_itr_valid(itr));
bool invalid = mt_invalid(old_mark);
if (old_mark.pos.row == row && old_mark.pos.col == col) {
// not paired: we can revise in place
if (!invalid && mt_decor_any(old_mark)) {
// TODO(bfredl): conflict of concerns: buf_decor_remove() must process
// the buffer as if MT_FLAG_DECOR_SIGNTEXT is already removed, however
// marktree must precisely adjust the set of flags from the old set to the new
uint16_t save_flags = mt_itr_rawkey(itr).flags;
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_DECOR_SIGNTEXT;
if (!mt_invalid(old_mark) && mt_decor_any(old_mark)) {
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
buf_decor_remove(buf, row, row, col, mt_decor(old_mark), true);
mt_itr_rawkey(itr).flags = save_flags;
}
marktree_revise_flags(buf->b_marktree, itr, flags);
mt_itr_rawkey(itr).flags |= flags;
mt_itr_rawkey(itr).decor_data = decor.data;
marktree_revise_meta(buf->b_marktree, itr, old_mark);
goto revised;
}
marktree_del_itr(buf->b_marktree, itr, false);
if (!invalid) {
if (!mt_invalid(old_mark)) {
buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.pos.col,
mt_decor(old_mark), true);
}
@ -131,6 +126,7 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool
int row2 = 0;
if (invalid) {
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID;
marktree_revise_meta(buf->b_marktree, itr, key);
} else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) {
MTPos end = marktree_get_altpos(buf->b_marktree, key, NULL);
row1 = MIN(end.row, MIN(key.pos.row, row));
@ -394,6 +390,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln
} else {
invalidated = true;
mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
marktree_revise_meta(buf->b_marktree, itr, mark);
buf_decor_remove(buf, mark.pos.row, endpos.row, mark.pos.col, mt_decor(mark), false);
}
}

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

@ -891,7 +891,17 @@ int get_breakindent_win(win_T *wp, char *line)
if (wp->w_briopt_list > 0) {
prev_list += wp->w_briopt_list;
} else {
prev_indent = (int)(*regmatch.endp - *regmatch.startp);
char *ptr = *regmatch.startp;
char *end_ptr = *regmatch.endp;
int indent = 0;
// Compute the width of the matched text.
// Use win_chartabsize() so that TAB size is correct,
// while wrapping is ignored.
while (ptr < end_ptr) {
indent += win_chartabsize(wp, ptr, indent);
MB_PTR_ADV(ptr);
}
prev_indent = indent;
}
}
vim_regfree(regmatch.regprog);
@ -1407,7 +1417,7 @@ void fixthisline(IndentGetter get_the_indent)
return;
}
change_indent(INDENT_SET, amount, false, 0, true);
change_indent(INDENT_SET, amount, false, true);
if (linewhite(curwin->w_cursor.lnum)) {
did_ai = true; // delete the indent if the line stays empty
}

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

@ -446,7 +446,7 @@ static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
// really meta_inc[kMTMetaCount]
static void meta_describe_key_inc(uint32_t *meta_inc, MTKey *k)
{
if (!mt_end(*k)) {
if (!mt_end(*k) && !mt_invalid(*k)) {
meta_inc[kMTMetaInline] += (k->flags & MT_FLAG_DECOR_VIRT_TEXT_INLINE) ? 1 : 0;
meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0;
meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0;
@ -774,14 +774,10 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
return other;
}
void marktree_revise_flags(MarkTree *b, MarkTreeIter *itr, uint16_t new_flags)
void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key)
{
uint32_t meta_old[4];
meta_describe_key(meta_old, rawkey(itr));
rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
rawkey(itr).flags |= new_flags;
uint32_t meta_new[4];
uint32_t meta_old[4], meta_new[4];
meta_describe_key(meta_old, old_key);
meta_describe_key(meta_new, rawkey(itr));
if (!memcmp(meta_old, meta_new, sizeof(meta_old))) {

View File

@ -523,12 +523,14 @@ int utf_ptr2cells(const char *p_in)
}
/// Convert a UTF-8 byte sequence to a character number.
/// Doesn't handle ascii! only multibyte and illegal sequences.
/// Doesn't handle ascii! only multibyte and illegal sequences. ASCII (including NUL)
/// are treated like illegal sequences.
///
/// @param[in] p String to convert.
/// @param[in] len Length of the character in bytes, 0 or 1 if illegal.
///
/// @return Unicode codepoint. A negative value when the sequence is illegal.
/// @return Unicode codepoint. A negative value when the sequence is illegal (or
/// ASCII, including NUL).
int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@ -1780,15 +1782,15 @@ int utf_head_off(const char *base_in, const char *p_in)
start--;
}
uint8_t cur_len = utf8len_tab[*start];
int32_t cur_code = utf_ptr2CharInfo_impl(start, (uintptr_t)cur_len);
if (cur_code < 0) {
const uint8_t last_len = utf8len_tab[*start];
int32_t cur_code = utf_ptr2CharInfo_impl(start, (uintptr_t)last_len);
if (cur_code < 0 || p - start >= last_len) {
return 0; // p must be part of an illegal sequence
}
const uint8_t * const safe_end = start + cur_len;
const uint8_t * const safe_end = start + last_len;
int cur_bc = utf8proc_get_property(cur_code)->boundclass;
if (always_break(cur_bc)) {
if (always_break(cur_bc) || start == base) {
return (int)(p - start);
}
@ -1796,18 +1798,23 @@ int utf_head_off(const char *base_in, const char *p_in)
const uint8_t *cur_pos = start;
const uint8_t *const p_start = start;
if (start == base) {
return (int)(p - start);
}
while (true) {
if (start[-1] == NUL) {
break;
}
start--;
if (*start < 0x80) { // stop on ascii, we are done
break;
}
start--;
while (*start >= 0x80) { // stop on ascii, we are done
while (start > base && (*start & 0xc0) == 0x80 && (cur_pos - start) < 6) {
start--;
}
int32_t prev_code = utf_ptr2CharInfo_impl(start, (uintptr_t)utf8len_tab[*start]);
if (prev_code < 0) {
int prev_len = utf8len_tab[*start];
int32_t prev_code = utf_ptr2CharInfo_impl(start, (uintptr_t)prev_len);
if (prev_code < 0 || prev_len < cur_pos - start) {
start = cur_pos; // start at valid sequence after invalid bytes
break;
}
@ -1822,12 +1829,10 @@ int utf_head_off(const char *base_in, const char *p_in)
cur_pos = start;
cur_bc = prev_bc;
cur_code = prev_code;
start--;
}
// hot path: we are already on the first codepoint of a sequence
if (start == p_start) {
if (start == p_start && last_len > p - start) {
return (int)(p - start);
}
@ -2920,17 +2925,17 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
emsg(_(e_listreq));
return;
}
const list_T *const l = argvars[0].vval.v_list;
if (tv_list_len(l) == 0) {
cw_interval_T *table = NULL;
const size_t table_size = (size_t)tv_list_len(l);
if (table_size == 0) {
// Clearing the table.
xfree(cw_table);
cw_table = NULL;
cw_table_size = 0;
return;
goto update;
}
// Note: use list_T instead of listitem_T so that TV_LIST_ITEM_NEXT can be used properly below.
const list_T **ptrs = xmalloc(sizeof(const list_T *) * (size_t)tv_list_len(l));
const list_T **ptrs = xmalloc(sizeof(const list_T *) * table_size);
// Check that all entries are a list with three numbers, the range is
// valid and the cell width is valid.
@ -2982,12 +2987,12 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
});
// Sort the list on the first number.
qsort((void *)ptrs, (size_t)tv_list_len(l), sizeof(const list_T *), tv_nr_compare);
qsort((void *)ptrs, table_size, sizeof(const list_T *), tv_nr_compare);
cw_interval_T *table = xmalloc(sizeof(cw_interval_T) * (size_t)tv_list_len(l));
table = xmalloc(sizeof(cw_interval_T) * table_size);
// Store the items in the new table.
for (item = 0; item < tv_list_len(l); item++) {
for (item = 0; (size_t)item < table_size; item++) {
const list_T *const li_l = ptrs[item];
const listitem_T *lili = tv_list_first(li_l);
const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number;
@ -3006,10 +3011,12 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree((void *)ptrs);
update:
;
cw_interval_T *const cw_table_save = cw_table;
const size_t cw_table_size_save = cw_table_size;
cw_table = table;
cw_table_size = (size_t)tv_list_len(l);
cw_table_size = table_size;
// Check that the new value does not conflict with 'listchars' or
// 'fillchars'.

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

@ -306,7 +306,7 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
// Set new indent
if (State & VREPLACE_FLAG) {
change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
change_indent(INDENT_SET, count, false, call_changed_bytes);
} else {
set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
}

View File

@ -761,9 +761,9 @@ return {
list:{n} Adds an additional indent for lines that match a
numbered or bulleted list (using the
'formatlistpat' setting).
list:-1 Uses the length of a match with 'formatlistpat'
for indentation.
(default: 0)
list:-1 Uses the width of a match with 'formatlistpat' for
indentation.
column:{n} Indent at column {n}. Will overrule the other
sub-options. Note: an additional indent may be
added for the 'showbreak' setting.

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

@ -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

@ -400,7 +400,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
}
if (second_indent >= 0) {
if (State & VREPLACE_FLAG) {
change_indent(INDENT_SET, second_indent, false, NUL, true);
change_indent(INDENT_SET, second_indent, false, true);
} else if (leader_len > 0 && second_indent - leader_len > 0) {
int padding = second_indent - leader_len;

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

@ -351,4 +351,97 @@ describe('insert-mode', function()
eq(2, api.nvim_win_get_cursor(0)[1])
end)
end)
it('backspace after replacing multibyte chars', function()
local screen = Screen.new(30, 3)
screen:attach()
api.nvim_buf_set_lines(0, 0, -1, true, { 'test ȧ̟̜̝̅̚m̆̉̐̐̇̈ å' })
feed('^Rabcdefghi')
screen:expect([[
abcdefghi^ |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcdefgh^å |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcdefg^ å |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcdef^m̆̉̐̐̇̈ å |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcde^ȧ̟̜̝̅̚m̆̉̐̐̇̈ å |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcd^ ȧ̟̜̝̅̚m̆̉̐̐̇̈ å |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<esc>')
api.nvim_buf_set_lines(0, 0, -1, true, { 'wow 🧑🌾🏳x' })
feed('^Rabcd')
screen:expect([[
abcd^🧑🌾🏳x |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('e')
screen:expect([[
abcde^🏳x |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('f')
screen:expect([[
abcdef^x |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcde^🏳x |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abcd^🧑🌾🏳x |
{1:~ }|
{5:-- REPLACE --} |
]])
feed('<bs>')
screen:expect([[
abc^ 🧑🌾🏳x |
{1:~ }|
{5:-- REPLACE --} |
]])
end)
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

@ -5641,6 +5641,19 @@ l5
]])
eq("Invalid 'sign_text'", pcall_err(api.nvim_buf_set_extmark, 0, ns, 5, 0, {sign_text='x'}))
end)
it('auto signcolumn hides with invalidated sign', function()
api.nvim_set_option_value('signcolumn', 'auto', {})
api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S1', invalidate=true})
feed('ia<cr>b<esc>dd')
screen:expect({
grid = [[
^a |
{1:~ }|*8
|
]]
})
end)
end)
describe('decorations: virt_text', 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

@ -837,18 +837,73 @@ func Test_breakindent20_list()
\ ]
let lines = s:screen_lines2(1, 9, 20)
call s:compare_lines(expect, lines)
" check with TABs
call setline(1, ["\t1.\tCongress shall make no law",
\ "\t2.) Congress shall make no law",
\ "\t3.] Congress shall make no law"])
setl tabstop=4 list listchars=tab:<->
norm! 1gg
redraw!
let expect = [
\ "<-->1.<>Congress ",
\ " shall make ",
\ " no law ",
\ "<-->2.) Congress ",
\ " shall make ",
\ " no law ",
\ "<-->3.] Congress ",
\ " shall make ",
\ " no law ",
\ ]
let lines = s:screen_lines2(1, 9, 20)
call s:compare_lines(expect, lines)
setl tabstop=2 nolist
redraw!
let expect = [
\ " 1. Congress ",
\ " shall make no ",
\ " law ",
\ " 2.) Congress ",
\ " shall make no ",
\ " law ",
\ " 3.] Congress ",
\ " shall make no ",
\ " law ",
\ ]
let lines = s:screen_lines2(1, 9, 20)
call s:compare_lines(expect, lines)
setl tabstop& list listchars=space:_
redraw!
let expect = [
\ "^I1.^ICongress_ ",
\ " shall_make_no_",
\ " law ",
\ "^I2.)_Congress_ ",
\ " shall_make_no_",
\ " law ",
\ "^I3.]_Congress_ ",
\ " shall_make_no_",
\ " law ",
\ ]
let lines = s:screen_lines2(1, 9, 20)
call s:compare_lines(expect, lines)
" check formatlistpat indent with different list levels
let &l:flp = '^\s*\*\+\s\+'
let &l:flp = '^\s*\(\*\|•\)\+\s\+'
setl list&vim listchars&vim
%delete _
call setline(1, ['* Congress shall make no law',
\ '*** Congress shall make no law',
\ '••• Congress shall make no law',
\ '**** Congress shall make no law'])
norm! 1gg
redraw!
let expect = [
\ "* Congress shall ",
\ " make no law ",
\ "*** Congress shall ",
\ "••• Congress shall ",
\ " make no law ",
\ "**** Congress shall ",
\ " make no law ",
@ -864,7 +919,7 @@ func Test_breakindent20_list()
let expect = [
\ "* Congress shall ",
\ "> make no law ",
\ "*** Congress shall ",
\ "••• Congress shall ",
\ "> make no law ",
\ "**** Congress shall ",
\ "> make no law ",
@ -880,7 +935,7 @@ func Test_breakindent20_list()
let expect = [
\ "* Congress shall ",
\ "> make no law ",
\ "*** Congress shall ",
\ "••• Congress shall ",
\ "> make no law ",
\ "**** Congress shall ",
\ "> make no law ",

View File

@ -228,6 +228,9 @@ func Test_setcellwidths()
call setcellwidths([[0x2103, 0x2103, 2]])
redraw
call assert_equal(19, wincol())
call setcellwidths([])
redraw
call assert_equal((aw == 'single') ? 10 : 19, wincol())
endfor
set ambiwidth& isprint&
@ -252,15 +255,21 @@ func Test_setcellwidths()
call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:')
set listchars=tab:--\\u2192
set listchars=tab:--\\u2192 fillchars=stl:\\u2501
call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:')
set fillchars=stl:\\u2501
call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:')
call setcellwidths([[0x201c, 0x201d, 1]])
set listchars& fillchars& ambiwidth=double
set listchars=nbsp:\\u201c fillchars=vert:\\u201d
call assert_fails('call setcellwidths([])', 'E834:')
set listchars&
call assert_fails('call setcellwidths([])', 'E835:')
set fillchars&
call setcellwidths([])
set ambiwidth&
bwipe!
endfunc

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(...)

View File

@ -4,7 +4,6 @@ local itp = t.gen_itp(it)
local ffi = t.ffi
local eq = t.eq
local to_cstr = t.to_cstr
local ok = t.ok
local lib = t.cimport(
'./src/nvim/mbyte.h',
@ -302,7 +301,10 @@ describe('mbyte', function()
local mb_glyphs = {}
while pos < len do
local clen = lib.utfc_ptr2len(cstr + pos)
ok(clen > 0) -- otherwise we get stuck
if clen == 0 then
eq(0, string.byte(str, pos + 1)) -- only NUL bytes can has length zery
clen = 1 -- but skip it, otherwise we get stuck
end
if clen > 1 then
table.insert(mb_glyphs, string.sub(str, pos + 1, pos + clen))
end
@ -325,13 +327,18 @@ describe('mbyte', function()
-- stylua doesn't like ZWJ chars..
-- stylua: ignore start
check('hej och hå 🧑‍🌾!', { 'å', '🧑‍🌾' })
-- emoji only (various kinds of combinations, use g8 to see them)
-- emoji (various kinds of combinations, use g8 to see them)
check("🏳️‍⚧️🧑‍🌾❤️😂🏴‍☠️", {"🏳️‍⚧️", "🧑‍🌾", "❤️", "😂", "🏴‍☠️"})
check('🏳xy🧑🌾\r❤️😂å🏴‍☠️€', { '🏳️‍⚧️', '🧑‍🌾', '❤️', '😂', 'å', '🏴‍☠️', '€' })
check('🏳️‍⚧️\000🧑‍🌾\000❤️\000😂\000å\000🏴‍☠️\000€', { '🏳️‍⚧️', '🧑‍🌾', '❤️', '😂', 'å', '🏴‍☠️', '€' })
check('\195🏳️‍⚧️\198🧑‍🌾\165❤️\168\195😂\255🏴‍☠️\129€\165', { '🏳️‍⚧️', '🧑‍🌾', '❤️', '😂', '🏴‍☠️', '€' })
check('🇦🅱️ 🇦🇽 🇦🇨🇦 🇲🇽🇹🇱',{'🇦', '🅱️', '🇦🇽', '🇦🇨', '🇦', '🇲🇽', '🇹🇱'})
check('🏴󠁧󠁢󠁳󠁣󠁴󠁿🏴󠁧󠁢󠁷󠁬󠁳󠁿', {'🏴󠁧󠁢󠁳󠁣󠁴󠁿', '🏴󠁧󠁢󠁷󠁬󠁳󠁿'})
check('å\165ü\195aëq\168β\000\169\255', {'å', 'ü', 'ë', 'β', ''})
lib.p_arshape = true -- default
check('سلام', { 'س', 'لا', 'م' })
lib.p_arshape = false