Compare commits

...

21 Commits

Author SHA1 Message Date
dundargoc
bf9ed8dbb3 test: add termkey unit tests
Skipped importing the following unit tests from libtermkey as they'd
require introducing a lot of unused code or require more effort to port
than is probably worth:

- 05read
- 12strpkey
- 20canon
- 40ti-override
2024-09-10 16:58:35 +02:00
bfredl
4c5bce9cb4
Merge pull request #30295 from glepnir/nfloat
fix(highlight): floating windows inherit NormalFloat from global-ns
2024-09-10 11:11:07 +02:00
Justin M. Keyes
5d7853f229
refactor(os/input.c): rename os_inchar => input_get #30327
Problem:
The name `os_inchar` (from Vim's old `mch_inchar`) is ambiguous:
"inchar" sounds like it could be reading or enqueuing (setting) input.
Its docstring is also ambiguous.

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

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

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

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

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

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

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

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

Before:

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

After:

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

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

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

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

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

closes: vim/vim#15636

b584117b05

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-09-08 05:41:44 +08:00
Yi Ming
d338ec9cb2
fix(vim.ui.open): prefer xdg-open on WSL #30302
xdg-open is usually not installed in WSL. But if the user deliberately
installs it, presumably they want to prioritize it.
2024-09-07 14:14:37 -07:00
Justin M. Keyes
5ddf2ab768
test(lua): tbl_deep_extend "after second argument" #30297 2024-09-07 09:41:02 -07:00
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
52 changed files with 1895 additions and 651 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*

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

975
test/unit/termkey_spec.lua Normal file
View File

@ -0,0 +1,975 @@
local t = require('test.unit.testutil')
local itp = t.gen_itp(it)
local bit = require('bit')
--- @alias TermKeyKey {utf8: string, type: integer, modifiers: integer, code: {codepoint: integer, sym: any, number: integer}}
--- @class termkey
--- @field TERMKEY_CANON_SPACESYMBOL integer
--- @field TERMKEY_FLAG_SPACESYMBOL integer
--- @field TERMKEY_FLAG_UTF8 integer
--- @field TERMKEY_FORMAT_ALTISMETA integer
--- @field TERMKEY_FORMAT_CARETCTRL integer
--- @field TERMKEY_FORMAT_LONGMOD integer
--- @field TERMKEY_FORMAT_LOWERMOD integer
--- @field TERMKEY_FORMAT_LOWERSPACE integer
--- @field TERMKEY_FORMAT_MOUSE_POS integer
--- @field TERMKEY_FORMAT_SPACEMOD integer
--- @field TERMKEY_FORMAT_WRAPBRACKET integer
--- @field TERMKEY_KEYMOD_ALT integer
--- @field TERMKEY_KEYMOD_CTRL integer
--- @field TERMKEY_MOUSE_DRAG integer
--- @field TERMKEY_MOUSE_PRESS integer
--- @field TERMKEY_MOUSE_RELEASE integer
--- @field TERMKEY_RES_AGAIN integer
--- @field TERMKEY_RES_KEY integer
--- @field TERMKEY_RES_NONE integer
--- @field TERMKEY_SYM_DOWN integer
--- @field TERMKEY_SYM_PAGEUP integer
--- @field TERMKEY_SYM_SPACE integer
--- @field TERMKEY_SYM_UNKNOWN integer
--- @field TERMKEY_SYM_UP integer
--- @field TERMKEY_TYPE_DCS integer
--- @field TERMKEY_TYPE_FUNCTION integer
--- @field TERMKEY_TYPE_KEYSYM integer
--- @field TERMKEY_TYPE_MODEREPORT integer
--- @field TERMKEY_TYPE_MOUSE integer
--- @field TERMKEY_TYPE_OSC integer
--- @field TERMKEY_TYPE_POSITION integer
--- @field TERMKEY_TYPE_UNICODE integer
--- @field TERMKEY_TYPE_UNKNOWN_CSI integer
--- @field termkey_canonicalise fun(any, any):any
--- @field termkey_destroy fun(any)
--- @field termkey_get_buffer_remaining fun(any):integer
--- @field termkey_get_buffer_size fun(any):integer
--- @field termkey_get_canonflags fun(any):any
--- @field termkey_get_keyname fun(any, any):any
--- @field termkey_getkey fun(any, any):any
--- @field termkey_getkey_force fun(any, any):any
--- @field termkey_interpret_csi fun(any, any, any, any, any):any
--- @field termkey_interpret_modereport fun(any, any, any, any, any):any
--- @field termkey_interpret_mouse fun(any, any, TermKeyKey, integer, integer, integer):any
--- @field termkey_interpret_position fun(any, any, any, any):any
--- @field termkey_interpret_string fun(any, TermKeyKey, any):any
--- @field termkey_lookup_keyname fun(any, any, any):any
--- @field termkey_new_abstract fun(string, integer):any
--- @field termkey_push_bytes fun(any, string, integer):integer
--- @field termkey_set_buffer_size fun(any, integer):integer
--- @field termkey_set_canonflags fun(any, any):any
--- @field termkey_set_flags fun(any, integer)
--- @field termkey_start fun(any):integer
--- @field termkey_stop fun(any):integer
--- @field termkey_strfkey fun(any, string, integer, any, any):integer
local termkey = t.cimport(
'./src/nvim/tui/termkey/termkey.h',
'./src/nvim/tui/termkey/termkey-internal.h',
'./src/nvim/tui/termkey/termkey_defs.h',
'./src/nvim/tui/termkey/driver-csi.h'
)
describe('termkey', function()
itp('01base', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
t.neq(tk, nil)
t.eq(termkey.termkey_get_buffer_size(tk), 256)
t.eq(tk.is_started, 1) -- tk->is_started true after construction
termkey.termkey_stop(tk)
t.neq(tk.is_started, 1) -- tk->is_started false after termkey_stop()
termkey.termkey_start(tk)
t.eq(tk.is_started, 1) -- tk->is_started true after termkey_start()
termkey.termkey_destroy(tk)
end)
itp('02getkey', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE when empty
t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1
t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after h
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after h
t.eq(key.code.codepoint, string.byte('h')) -- key.code.codepoint after h
t.eq(key.modifiers, 0) -- key.modifiers after h
t.eq(t.ffi.string(key.utf8), 'h') -- key.utf8 after h
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free 256 after getkey
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE a second time
termkey.termkey_push_bytes(tk, '\x01', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after C-a
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after C-a
t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint after C-a
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- key.modifiers after C-a
termkey.termkey_push_bytes(tk, '\033OA', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Up
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Up");
-- is_int(key.code.sym, TERMKEY_SYM_UP, "key.code.sym after Up");
t.eq(key.modifiers, 0) -- key.modifiers after Up
t.eq(termkey.termkey_push_bytes(tk, '\033O', 2), 2) -- push_bytes returns 2
-- is_int(termkey_get_buffer_remaining(tk), 254, "buffer free 254 after partial write");
-- is_int(termkey_getkey(tk, &key), TERMKEY_RES_AGAIN, "getkey yields RES_AGAIN after partial write");
termkey.termkey_push_bytes(tk, 'C', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Right completion
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Right");
-- is_int(key.code.sym, TERMKEY_SYM_RIGHT, "key.code.sym after Right");
-- is_int(key.modifiers, 0, "key.modifiers after Right");
-- is_int(termkey_get_buffer_remaining(tk), 256, "buffer free 256 after completion");
termkey.termkey_push_bytes(tk, '\033[27;5u', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Escape
-- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Ctrl-Escape");
-- is_int(key.code.sym, TERMKEY_SYM_ESCAPE, "key.code.sym after Ctrl-Escape");
-- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Escape");
termkey.termkey_push_bytes(tk, '\0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Space
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after Ctrl-Space
-- t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after Ctrl-Space
-- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Space");
termkey.termkey_destroy(tk)
end)
itp('03utf8', function()
local tk = termkey.termkey_new_abstract('vt100', termkey.TERMKEY_FLAG_UTF8)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
termkey.termkey_push_bytes(tk, 'a', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY low ASCII
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type low ASCII
t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint low ASCII
-- 2-byte UTF-8 range is U+0080 to U+07FF (0xDF 0xBF)
-- However, we'd best avoid the C1 range, so we'll start at U+00A0 (0xC2 0xA0)
termkey.termkey_push_bytes(tk, '\xC2\xA0', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 low
t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 low
termkey.termkey_push_bytes(tk, '\xDF\xBF', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 high
t.eq(key.code.codepoint, 0x07FF) -- key.code.codepoint UTF-8 2 high
-- 3-byte UTF-8 range is U+0800 (0xE0 0xA0 0x80) to U+FFFD (0xEF 0xBF 0xBD)
termkey.termkey_push_bytes(tk, '\xE0\xA0\x80', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 low
t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 low
termkey.termkey_push_bytes(tk, '\xEF\xBF\xBD', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 high
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 high
-- 4-byte UTF-8 range is U+10000 (0xF0 0x90 0x80 0x80) to U+10FFFF (0xF4 0x8F 0xBF 0xBF)
termkey.termkey_push_bytes(tk, '\xF0\x90\x80\x80', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 low
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 low
t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 low
termkey.termkey_push_bytes(tk, '\xF4\x8F\xBF\xBF', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 high
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 high
t.eq(key.code.codepoint, 0x10FFFF) -- key.code.codepoint UTF-8 4 high
-- Invalid continuations
termkey.termkey_push_bytes(tk, '\xC2!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 2 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 2 invalid after
termkey.termkey_push_bytes(tk, '\xE0!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after
termkey.termkey_push_bytes(tk, '\xE0\xA0!', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont 2
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont 2
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after
termkey.termkey_push_bytes(tk, '\xF0!', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
termkey.termkey_push_bytes(tk, '\xF0\x90!', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 2
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 2
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
termkey.termkey_push_bytes(tk, '\xF0\x90\x80!', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 3
t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 3
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after
t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after
-- Partials
termkey.termkey_push_bytes(tk, '\xC2', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 2 partial
termkey.termkey_push_bytes(tk, '\xA0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 partial
t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 partial
termkey.termkey_push_bytes(tk, '\xE0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\xA0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 partial
t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 partial
termkey.termkey_push_bytes(tk, '\xF0', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x90', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial
termkey.termkey_push_bytes(tk, '\x80', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 partial
t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 partial
termkey.termkey_destroy(tk)
end)
itp('04flags', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
termkey.termkey_push_bytes(tk, ' ', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after space
t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after space
t.eq(key.modifiers, 0) -- key.modifiers after space
termkey.termkey_set_flags(tk, termkey.TERMKEY_FLAG_SPACESYMBOL)
termkey.termkey_push_bytes(tk, ' ', 1)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space
t.eq(key.type, termkey.TERMKEY_TYPE_KEYSYM) -- key.type after space with FLAG_SPACESYMBOL
t.eq(key.code.sym, termkey.TERMKEY_SYM_SPACE) -- key.code.sym after space with FLAG_SPACESYMBOL
t.eq(key.modifiers, 0) -- key.modifiers after space with FLAG_SPACESYMBOL
termkey.termkey_destroy(tk)
end)
itp('06buffer', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256
t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size initially 256
t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1
t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes
t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size 256 after push_bytes
t.eq(not not termkey.termkey_set_buffer_size(tk, 512), true) -- buffer set size OK
t.eq(termkey.termkey_get_buffer_remaining(tk), 511) -- buffer free 511 after push_bytes
t.eq(termkey.termkey_get_buffer_size(tk), 512) -- buffer size 512 after push_bytes
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- buffered key still useable after resize
termkey.termkey_destroy(tk)
end)
local function termkey_keyname2sym(tk, keyname)
local sym = t.ffi.new('TermKeySym[1]')
local endp = termkey.termkey_lookup_keyname(tk, keyname, sym)
if endp == nil then
return termkey.TERMKEY_SYM_UNKNOWN
end
return sym
end
itp('10keyname', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local sym = termkey_keyname2sym(tk, 'SomeUnknownKey')
t.eq(sym, termkey.TERMKEY_SYM_UNKNOWN) -- keyname2sym SomeUnknownKey
sym = termkey_keyname2sym(tk, 'Space')
t.eq(sym[0], termkey.TERMKEY_SYM_SPACE) -- keyname2sym Space
local _end = termkey.termkey_lookup_keyname(tk, 'Up', sym)
t.neq(_end, nil) -- termkey_get_keyname Up returns non-NULL
t.eq(t.ffi.string(_end), '') -- termkey_get_keyname Up return points at endofstring
t.eq(sym[0], termkey.TERMKEY_SYM_UP) -- termkey_get_keyname Up yields Up symbol
_end = termkey.termkey_lookup_keyname(tk, 'DownMore', sym)
t.neq(_end, nil) -- termkey_get_keyname DownMore returns non-NULL
t.eq(t.ffi.string(_end), 'More') -- termkey_get_keyname DownMore return points at More
t.eq(sym[0], termkey.TERMKEY_SYM_DOWN) -- termkey_get_keyname DownMore yields Down symbol
_end = termkey.termkey_lookup_keyname(tk, 'SomeUnknownKey', sym)
t.eq(_end, nil) -- termkey_get_keyname SomeUnknownKey returns NULL
t.eq(t.ffi.string(termkey.termkey_get_keyname(tk, termkey.TERMKEY_SYM_SPACE)), 'Space') -- "get_keyname SPACE");
termkey.termkey_destroy(tk)
end)
itp('11strfkey', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
---@type TermKeyKey
local key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_UNICODE, code = { codepoint = string.byte('A') } }
)
local buffer = t.ffi.new('char[16]')
local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 1) -- length for unicode/A/0
t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 1) -- length for unicode/A/0 wrapbracket
t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 wrapbracket
---@type TermKeyKey
key = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('b') },
modifiers = termkey.TERMKEY_KEYMOD_CTRL,
})
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 3) -- length for unicode/b/CTRL
t.eq(t.ffi.string(buffer), 'C-b') -- buffer for unicode/b/CTRL
len =
termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD)
t.eq(len, 6) -- length for unicode/b/CTRL longmod
t.eq(t.ffi.string(buffer), 'Ctrl-b') -- buffer for unicode/b/CTRL longmod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_SPACEMOD)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod
t.eq(t.ffi.string(buffer), 'Ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_LOWERMOD)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|lowermod
t.eq(t.ffi.string(buffer), 'ctrl-b') -- buffer for unicode/b/CTRL longmod|lowermod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(
termkey.TERMKEY_FORMAT_LONGMOD,
termkey.TERMKEY_FORMAT_SPACEMOD,
termkey.TERMKEY_FORMAT_LOWERMOD
)
)
t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod|lowermode
t.eq(t.ffi.string(buffer), 'ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod|lowermode
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_CARETCTRL
)
t.eq(len, 2) -- length for unicode/b/CTRL caretctrl
t.eq(t.ffi.string(buffer), '^B') -- buffer for unicode/b/CTRL caretctrl
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 5) -- length for unicode/b/CTRL wrapbracket
t.eq(t.ffi.string(buffer), '<C-b>') -- buffer for unicode/b/CTRL wrapbracket
---@type TermKeyKey
key = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('c') },
modifiers = termkey.TERMKEY_KEYMOD_ALT,
})
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 3) -- length for unicode/c/ALT
t.eq(t.ffi.string(buffer), 'A-c') -- buffer for unicode/c/ALT
len =
termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD)
t.eq(len, 5) -- length for unicode/c/ALT longmod
t.eq(t.ffi.string(buffer), 'Alt-c') -- buffer for unicode/c/ALT longmod
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_ALTISMETA
)
t.eq(len, 3) -- length for unicode/c/ALT altismeta
t.eq(t.ffi.string(buffer), 'M-c') -- buffer for unicode/c/ALT altismeta
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_ALTISMETA)
)
t.eq(len, 6) -- length for unicode/c/ALT longmod|altismeta
t.eq(t.ffi.string(buffer), 'Meta-c') -- buffer for unicode/c/ALT longmod|altismeta
---@type TermKeyKey
key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_UP } }
)
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 2) -- length for sym/Up/0
t.eq(t.ffi.string(buffer), 'Up') -- buffer for sym/Up/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 4) -- length for sym/Up/0 wrapbracket
t.eq(t.ffi.string(buffer), '<Up>') -- buffer for sym/Up/0 wrapbracket
---@type TermKeyKey
key = t.ffi.new(
'TermKeyKey',
{ type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_PAGEUP } }
)
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 6) -- length for sym/PageUp/0
t.eq(t.ffi.string(buffer), 'PageUp') -- buffer for sym/PageUp/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_LOWERSPACE
)
t.eq(len, 7) -- length for sym/PageUp/0 lowerspace
t.eq(t.ffi.string(buffer), 'page up') -- buffer for sym/PageUp/0 lowerspace
-- If size of buffer is too small, strfkey should return something consistent
len = termkey.termkey_strfkey(tk, buffer, 4, key, 0)
t.eq(len, 6) -- length for sym/PageUp/0
t.eq(t.ffi.string(buffer), 'Pag') -- buffer of len 4 for sym/PageUp/0
len = termkey.termkey_strfkey(tk, buffer, 4, key, termkey.TERMKEY_FORMAT_LOWERSPACE)
t.eq(len, 7) -- length for sym/PageUp/0 lowerspace
t.eq(t.ffi.string(buffer), 'pag') -- buffer of len 4 for sym/PageUp/0 lowerspace
key = t.ffi.new('TermKeyKey', { type = termkey.TERMKEY_TYPE_FUNCTION, code = { number = 5 } }) ---@type TermKeyKey
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 2) -- length for func/5/0
t.eq(t.ffi.string(buffer), 'F5') -- buffer for func/5/0
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_WRAPBRACKET
)
t.eq(len, 4) -- length for func/5/0 wrapbracket
t.eq(t.ffi.string(buffer), '<F5>') -- buffer for func/5/0 wrapbracket
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_LOWERSPACE
)
t.eq(len, 2) -- length for func/5/0 lowerspace
t.eq(t.ffi.string(buffer), 'f5') -- buffer for func/5/0 lowerspace
termkey.termkey_destroy(tk)
end)
itp('13cmpkey', function()
local function termkey_keycmp(tk, key1, key2)
termkey.termkey_canonicalise(tk, key1)
termkey.termkey_canonicalise(tk, key2)
if key1.type ~= key2.type then
return key1.type - key2.type
end
if key1.type == termkey.TERMKEY_TYPE_UNICODE then
if key1.code.codepoint ~= key2.code.codepoint then
return key1.code.codepoint - key2.code.codepoint
end
end
return key1.modifiers - key2.modifiers
end
local tk = termkey.termkey_new_abstract('vt100', 0)
---@type TermKeyKey
local key1 = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('A') },
modifiers = 0,
})
---@type TermKeyKey
local key2 = t.ffi.new('TermKeyKey', {
type = termkey.TERMKEY_TYPE_UNICODE,
code = { codepoint = string.byte('A') },
modifiers = 0,
})
t.eq(termkey_keycmp(tk, key1, key1), 0) -- cmpkey same structure
t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey identical structure
key2.modifiers = termkey.TERMKEY_KEYMOD_CTRL
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders CTRL after nomod
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders nomod before CTRL
key2.code.codepoint = string.byte('B')
key2.modifiers = 0
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders 'B' after 'A'
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders 'A' before 'B'
key1.modifiers = termkey.TERMKEY_KEYMOD_CTRL
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders nomod 'B' after CTRL 'A'
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders CTRL 'A' before nomod 'B'
key2.type = termkey.TERMKEY_TYPE_KEYSYM
key2.code.sym = termkey.TERMKEY_SYM_UP
t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders KEYSYM after UNICODE
t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders UNICODE before KEYSYM
key1.type = termkey.TERMKEY_TYPE_KEYSYM
key1.code.sym = termkey.TERMKEY_SYM_SPACE
key1.modifiers = 0
key2.type = termkey.TERMKEY_TYPE_UNICODE
key2.code.codepoint = string.byte(' ')
key2.modifiers = 0
t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey considers KEYSYM/SPACE and UNICODE/SP identical
termkey.termkey_set_canonflags(
tk,
bit.bor(termkey.termkey_get_canonflags(tk), termkey.TERMKEY_CANON_SPACESYMBOL)
)
t.eq(termkey_keycmp(tk, key1, key2), 0) -- "cmpkey considers KEYSYM/SPACE and UNICODE/SP identical under SPACESYMBOL");
termkey.termkey_destroy(tk)
end)
itp('30mouse', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey', { type = -1 }) ---@type TermKeyKey
local ev = t.ffi.new('TermKeyMouseEvent[1]')
local button = t.ffi.new('int[1]')
local line = t.ffi.new('int[1]')
local col = t.ffi.new('int[1]')
local buffer = t.ffi.new('char[32]')
termkey.termkey_push_bytes(tk, '\x1b[M !!', 6)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press
t.eq(button[0], 1) -- mouse button for press
t.eq(line[0], 1) -- mouse line for press
t.eq(col[0], 1) -- mouse column for press
t.eq(key.modifiers, 0) -- modifiers for press
local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 13) -- string length for press
t.eq(t.ffi.string(buffer), 'MousePress(1)') -- string buffer for press
len = termkey.termkey_strfkey(
tk,
buffer,
t.ffi.sizeof(buffer),
key,
termkey.TERMKEY_FORMAT_MOUSE_POS
)
t.eq(len, 21) -- string length for press
t.eq(t.ffi.string(buffer), 'MousePress(1) @ (1,1)') -- string buffer for press
termkey.termkey_push_bytes(tk, '\x1b[M@"!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_DRAG) -- mouse event for drag
t.eq(button[0], 1) -- mouse button for drag
t.eq(line[0], 1) -- mouse line for drag
t.eq(col[0], 2) -- mouse column for drag
t.eq(key.modifiers, 0) -- modifiers for press
termkey.termkey_push_bytes(tk, '\x1b[M##!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release
t.eq(line[0], 1) -- mouse line for release
t.eq(col[0], 3) -- mouse column for release
t.eq(key.modifiers, 0) -- modifiers for press
termkey.termkey_push_bytes(tk, '\x1b[M0++', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for Ctrl-press
t.eq(button[0], 1) -- mouse button for Ctrl-press
t.eq(line[0], 11) -- mouse line for Ctrl-press
t.eq(col[0], 11) -- mouse column for Ctrl-press
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- modifiers for Ctrl-press
len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0)
t.eq(len, 15) -- string length for Ctrl-press
t.eq(t.ffi.string(buffer), 'C-MousePress(1)') -- string buffer for Ctrl-press
termkey.termkey_push_bytes(tk, '\x1b[M`!!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel down
t.eq(button[0], 4) -- mouse button for wheel down
termkey.termkey_push_bytes(tk, '\x1b[Mb!!', 6)
termkey.termkey_getkey(tk, key)
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel left
t.eq(button[0], 6) -- mouse button for wheel left
-- rxvt protocol
termkey.termkey_push_bytes(tk, '\x1b[0;20;20M', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press rxvt protocol
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press rxvt protocol
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press rxvt protocol
t.eq(button[0], 1) -- mouse button for press rxvt protocol
t.eq(line[0], 20) -- mouse line for press rxvt protocol
t.eq(col[0], 20) -- mouse column for press rxvt protocol
t.eq(key.modifiers, 0) -- modifiers for press rxvt protocol
termkey.termkey_push_bytes(tk, '\x1b[3;20;20M', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release rxvt protocol
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release rxvt protocol
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release rxvt protocol
t.eq(line[0], 20) -- mouse line for release rxvt protocol
t.eq(col[0], 20) -- mouse column for release rxvt protocol
t.eq(key.modifiers, 0) -- modifiers for release rxvt protocol
-- SGR protocol
termkey.termkey_push_bytes(tk, '\x1b[<0;30;30M', 11)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press SGR encoding
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press SGR encoding
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press SGR
t.eq(button[0], 1) -- mouse button for press SGR
t.eq(line[0], 30) -- mouse line for press SGR
t.eq(col[0], 30) -- mouse column for press SGR
t.eq(key.modifiers, 0) -- modifiers for press SGR
termkey.termkey_push_bytes(tk, '\x1b[<0;30;30m', 11)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release SGR encoding
t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release SGR encoding
t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY
t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release SGR
termkey.termkey_push_bytes(tk, '\x1b[<0;500;300M', 13)
termkey.termkey_getkey(tk, key)
termkey.termkey_interpret_mouse(tk, key, ev, button, line, col)
t.eq(line[0], 300) -- mouse line for press SGR wide
t.eq(col[0], 500) -- mouse column for press SGR wide
termkey.termkey_destroy(tk)
end)
itp('31position', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local line = t.ffi.new('int[1]')
local col = t.ffi.new('int[1]')
termkey.termkey_push_bytes(tk, '\x1b[?15;7R', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for position report
t.eq(key.type, termkey.TERMKEY_TYPE_POSITION) -- key.type for position report
t.eq(termkey.termkey_interpret_position(tk, key, line, col), termkey.TERMKEY_RES_KEY) -- interpret_position yields RES_KEY
t.eq(line[0], 15) -- line for position report
t.eq(col[0], 7) -- column for position report
-- A plain CSI R is likely to be <F3> though.
-- This is tricky :/
termkey.termkey_push_bytes(tk, '\x1b[R', 3)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for <F3>
t.eq(key.type, termkey.TERMKEY_TYPE_FUNCTION) -- key.type for <F3>
t.eq(key.code.number, 3) -- key.code.number for <F3>
termkey.termkey_destroy(tk)
end)
itp('32modereport', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local initial = t.ffi.new('int[1]')
local mode = t.ffi.new('int[1]')
local value = t.ffi.new('int[1]')
termkey.termkey_push_bytes(tk, '\x1b[?1;2$y', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report
t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report
t.eq(
termkey.termkey_interpret_modereport(tk, key, initial, mode, value),
termkey.TERMKEY_RES_KEY
) -- interpret_modereoprt yields RES_KEY
t.eq(initial[0], 63) -- initial indicator from mode report
t.eq(mode[0], 1) -- mode number from mode report
t.eq(value[0], 2) -- mode value from mode report
termkey.termkey_push_bytes(tk, '\x1b[4;1$y', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report
t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report
t.eq(
termkey.termkey_interpret_modereport(tk, key, initial, mode, value),
termkey.TERMKEY_RES_KEY
) -- interpret_modereoprt yields RES_KEY
t.eq(initial[0], 0) -- initial indicator from mode report
t.eq(mode[0], 4) -- mode number from mode report
t.eq(value[0], 1) -- mode value from mode report
termkey.termkey_destroy(tk)
end)
itp('38csi', function()
local tk = termkey.termkey_new_abstract('vt100', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
local args = t.ffi.new('TermKeyCsiParam[16]')
local nargs = t.ffi.new('size_t[1]')
local command = t.ffi.new('unsigned[1]')
termkey.termkey_push_bytes(tk, '\x1b[5;25v', 7)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI v
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(nargs[0], 2) -- nargs for unknown CSI
-- t.eq(args[0], 5) -- args[0] for unknown CSI
-- t.eq(args[1], 25) -- args[1] for unknown CSI
t.eq(command[0], 118) -- command for unknown CSI
termkey.termkey_push_bytes(tk, '\x1b[?w', 4)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? w
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(command[0], bit.bor(bit.lshift(63, 8), 119)) -- command for unknown CSI
termkey.termkey_push_bytes(tk, '\x1b[?$x', 5)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? $x
t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI
t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY
t.eq(command[0], bit.bor(bit.lshift(36, 16), bit.lshift(63, 8), 120)) -- command for unknown CSI
termkey.termkey_destroy(tk)
end)
itp('39dcs', function()
local tk = termkey.termkey_new_abstract('xterm', 0)
local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey
-- 7bit DCS
termkey.termkey_push_bytes(tk, '\x1bP1$r1 q\x1b\\', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS
t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS
t.eq(key.modifiers, 0) -- key.modifiers for DCS
local str = t.ffi.new('const char*[1]')
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- termkey_interpret_string() gives string
t.eq(t.ffi.string(str[0]), '1$r1 q') -- termkey_interpret_string() yields correct string
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE
-- 8bit DCS
termkey.termkey_push_bytes(tk, '\x901$r2 q\x9c', 8)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS
t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS
t.eq(key.modifiers, 0) -- key.modifiers for DCS
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string");
t.eq(t.ffi.string(str[0]), '1$r2 q') -- "termkey_interpret_string() yields correct string");
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- "getkey again yields RES_NONE");
-- 7bit OSC
termkey.termkey_push_bytes(tk, '\x1b]15;abc\x1b\\', 10)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for OSC
t.eq(key.type, termkey.TERMKEY_TYPE_OSC) -- key.type for OSC
t.eq(key.modifiers, 0) -- key.modifiers for OSC
t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string");
t.eq(t.ffi.string(str[0]), '15;abc') -- "termkey_interpret_string() yields correct string");
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE
-- False alarm
termkey.termkey_push_bytes(tk, '\x1bP', 2)
t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN for false alarm
t.eq(termkey.termkey_getkey_force(tk, key), termkey.TERMKEY_RES_KEY) -- getkey_force yields RES_KEY for false alarm
t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type for false alarm
t.eq(key.code.codepoint, string.byte('P')) -- key.code.codepoint for false alarm
t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_ALT) -- key.modifiers for false alarm
termkey.termkey_destroy(tk)
end)
end)