diff options
160 files changed, 4453 insertions, 1714 deletions
diff --git a/.clang-tidy b/.clang-tidy index e85ebb6758..3e931149d6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,15 +21,21 @@ Checks: > -bugprone-sizeof-expression, -bugprone-suspicious-include, -bugprone-suspicious-memory-comparison, + -bugprone-swapped-arguments, + -bugprone-switch-missing-default-case, + -bugprone-unsafe-functions, -bugprone-unused-return-value, -google-readability-braces-around-statements, -google-readability-function-size, + -misc-header-include-cycle, + -misc-include-cleaner, -misc-misplaced-const, -misc-no-recursion, -misc-unused-parameters, -modernize-macro-to-enum, -performance-no-int-to-ptr, -readability-avoid-const-params-in-decls, + -readability-avoid-unconditional-preprocessor-if, -readability-braces-around-statements, -readability-else-after-return, -readability-function-cognitive-complexity, @@ -38,7 +44,7 @@ Checks: > -readability-isolate-declaration, -readability-magic-numbers, -readability-misleading-indentation, + -readability-non-const-parameter, -readability-redundant-declaration, -readability-redundant-function-ptr-dereference, -readability-suspicious-call-argument, - -readability-non-const-parameter, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fe3c39bde..7eed75f0d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ low-risk/isolated tasks: - Try a [complexity:low] issue. - Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or [Coverity](#coverity). -- [Improve documentation](#documenting) +- [Improve documentation](#documentation) - [Merge a Vim patch] (requires strong familiarity with Vim) - NOTE: read the above link before sending improvements to "runtime files" (anything in `runtime/`). - Vimscript and documentation files are (mostly) maintained by [Vim](https://github.com/vim/vim), not Nvim. @@ -267,8 +267,8 @@ to `runtime/lua/vim/_editor.lua` then: VIMRUNTIME=./runtime ./build/bin/nvim --luamod-dev ``` -Documenting ------------ +Documentation +------------- Read [:help dev-doc][dev-doc-guide] to understand the expected documentation style and conventions. @@ -287,10 +287,15 @@ If you need to modify or debug the documentation flow, these are the main files: - `./scripts/lua2dox.lua`: Used by `gen_vimdoc.py` to transform Lua files into a format compatible with doxygen. - `./scripts/gen_eval_files.lua`: - Generates documentation and Lua type files from metadata files including: - - Eval functions (`src/nvim/eval.lua`) - - Options (`src/nvim/options.lua`) - - API + Generates documentation and Lua type files from metadata files: + ``` + runtime/lua/vim/* => runtime/doc/lua.txt + runtime/lua/vim/* => runtime/doc/lua.txt + runtime/lua/vim/lsp/ => runtime/doc/lsp.txt + src/nvim/api/* => runtime/doc/api.txt + src/nvim/eval.lua => runtime/doc/builtin.txt + src/nvim/options.lua => runtime/doc/options.txt + ``` ### Lua docstrings diff --git a/MAINTAIN.md b/MAINTAIN.md index 3e31fde118..f80f9b92fb 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -145,6 +145,8 @@ These dependencies are "vendored" (inlined), we must update the sources manually * Run `scripts/update_terminfo.sh` to update these definitions. * `runtime/lua/vim/lsp/_meta/protocol.lua`: LSP specification * Run `scripts/gen_lsp.lua` to update. +* `runtime/lua/vim/_meta/lpeg.lua`: LPeg definitions. + * Refer to [`LuaCATS/lpeg`](https://github.com/LuaCATS/lpeg) for updates. * `src/bit.c`: only for PUC lua: port of `require'bit'` from luajit https://bitop.luajit.org/ * [treesitter parsers](https://github.com/neovim/neovim/blob/fcc24e43e0b5f9d801a01ff2b8f78ce8c16dd551/cmake.deps/CMakeLists.txt#L197-L210) * `runtime/lua/coxpcall.lua`: coxpcall (only needed for PUC lua, builtin to luajit) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 945f8d5f45..bf12f1138c 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -4,8 +4,8 @@ LIBUV_SHA256 7aa66be3413ae10605e1f5c9ae934504ffe317ef68ea16fdaa83e23905c681bd MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/e897c5743f97a6b05c59852709092e7da4119914.tar.gz -LUAJIT_SHA256 f28030c61602bffd28a445f3fef23198264a845aab94d909587bac6c12c8b874 +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/becf5cc65d966a8926466dd43407c48bfea0fa13.tar.gz +LUAJIT_SHA256 6d7e8fc691d45fe837d05e2a03f3a41b0886a237544d30f74f1355ce2c8d9157 LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index be170d8aec..2dd5cf45bf 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -2951,7 +2951,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method) " to process this detection correctly. " call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand("<slnum>")) " call Decho("..did_filetype()=".did_filetype()) - setl ft= +" setl ft= " call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">") let iskkeep= &isk setl isk-=/ diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 6211b457d6..48b96c699a 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -26,7 +26,7 @@ endfunction function! s:get_python_candidates(major_version) abort return { - \ 3: ['python3', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python'] + \ 3: ['python3', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python'] \ }[a:major_version] endfunction @@ -61,12 +61,11 @@ endfunction " Returns array: [prog_exitcode, prog_version] function! s:import_module(prog, module) abort - let prog_version = system([a:prog, '-c' , printf( - \ 'import sys; ' . + let prog_version = system([a:prog, '-W', 'ignore', '-c', printf( + \ 'import sys, importlib.util; ' . \ 'sys.path = [p for p in sys.path if p != ""]; ' . \ 'sys.stdout.write(str(sys.version_info[0]) + "." + str(sys.version_info[1])); ' . - \ 'import pkgutil; ' . - \ 'exit(2*int(pkgutil.get_loader("%s") is None))', + \ 'sys.exit(2 * int(importlib.util.find_spec("%s") is None))', \ a:module)]) return [v:shell_error, prog_version] endfunction diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d30bb4851c..1eebd0bb18 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1507,7 +1507,7 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* • {rhs} Right-hand-side |{rhs}| of the mapping. • {opts} Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|, values are booleans (default false). Also: - • "noremap" non-recursive mapping |:noremap| + • "noremap" disables |recursive_mapping|, like |:noremap| • "desc" human-readable description. • "callback" Lua function called in place of {rhs}. • "replace_keycodes" (boolean) When "expr" is true, replace @@ -3179,6 +3179,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* • noautocmd: If true then no buffer-related autocommand events such as |BufEnter|, |BufLeave| or |BufWinEnter| may fire from calling this function. + • fixed: If true when anchor is NW or SW, the float window + would be kept fixed even if the window would be truncated. Return: ~ Window handle, or 0 on error diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 05db977809..207bf817b0 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3738,7 +3738,7 @@ jobresize({job}, {width}, {height}) *jobresize()* Fails if the job was not started with `"pty":v:true`. jobstart({cmd} [, {opts}]) *jobstart()* - Note: Prefer |vim.system()| in Lua. + Note: Prefer |vim.system()| in Lua (unless using the `pty` option). Spawns {cmd} as a job. If {cmd} is a List it runs directly (no 'shell'). @@ -7742,6 +7742,7 @@ strutf16len({string} [, {countcc}]) *strutf16len()* echo strutf16len('😊') " returns 2 echo strutf16len('ą́') " returns 1 echo strutf16len('ą́', v:true) " returns 3 +< strwidth({string}) *strwidth()* The result is a Number, which is the number of display cells diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 894557818b..2c47421b02 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -196,6 +196,7 @@ gR Enter Virtual Replace mode: Each character you type *v_r* {Visual}r{char} Replace all selected characters by {char}. + CTRL-C will be inserted literally. *v_C* {Visual}["x]C Delete the highlighted lines [into register x] and diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 2e9312cf74..407d7ae9fb 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -141,6 +141,7 @@ LSP FUNCTIONS `progress` of |vim.lsp.client| - *vim.lsp.get_active_clients()* Use |vim.lsp.get_clients()| - *vim.lsp.for_each_buffer_client()* Use |vim.lsp.get_clients()| +- *vim.lsp.util.trim_empty_lines()* Use |vim.split()| with `trimempty` instead. TREESITTER FUNCTIONS - *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()| diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 71c16659eb..f4c581fabe 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -312,6 +312,12 @@ See also |dev-naming|. - Avoid functions that depend on cursor position, current buffer, etc. Instead the function should take a position parameter, buffer parameter, etc. +Where things go ~ + +- API (libnvim/RPC): exposes low-level internals, or fundamental things (such + as `nvim_exec_lua()`) needed by clients or C consumers. +- Lua stdlib = high-level functionality that builds on top of the API. + NAMING GUIDELINES *dev-naming* Naming is exceedingly important: the name of a thing is the primary interface diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 2225e6e753..2f44256138 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -539,10 +539,10 @@ set to "dos", otherwise it is set to "unix". When 'fileformats' includes "mac". If the 'fileformat' option is set to "dos" on non-MS-Windows systems the -message "[dos format]" is shown to remind you that something unusual is -happening. On MS-Windows systems you get the message "[unix format]" if -'fileformat' is set to "unix". On all systems but the Macintosh you get the -message "[mac format]" if 'fileformat' is set to "mac". +message "[dos]" is shown to remind you that something unusual is happening. On +MS-Windows systems you get the message "[unix]" if 'fileformat' is set to +"unix". On all systems you get the message "[mac]" if 'fileformat' is set to +"mac". If the 'fileformats' option is empty and DOS format is used, but while reading a file some lines did not end in <CR><NL>, "[CR missing]" will be included in @@ -820,12 +820,10 @@ by the shell before executing the find program. When there is an argument list you can see which file you are editing in the title of the window (if there is one and 'title' is on) and with the file message you get with the "CTRL-G" command. You will see something like - (file 4 of 11) -If 'shortmess' contains 'f' it will be (4 of 11) If you are not really editing the file at the current position in the argument list it will be - (file (4) of 11) + ((4) of 11) This means that you are position 4 in the argument list, but not editing the fourth file in the argument list. This happens when you do ":e file". @@ -1058,14 +1056,14 @@ lost the original file. *DOS-format-write* If the 'fileformat' is "dos", <CR><NL> is used for <EOL>. This is default -for Windows. On other systems the message "[dos format]" is shown to +for Windows. On other systems the message "[dos]" is shown to remind you that an unusual <EOL> was used. *Unix-format-write* If the 'fileformat' is "unix", <NL> is used for <EOL>. On Windows -the message "[unix format]" is shown. +the message "[unix]" is shown. *Mac-format-write* -If the 'fileformat' is "mac", <CR> is used for <EOL>. On non-Mac systems the -message "[mac format]" is shown. +If the 'fileformat' is "mac", <CR> is used for <EOL>. The +message "[mac]" is shown. See also |file-formats| and the 'fileformat' and 'fileformats' options. diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt index b912f732b6..083b6f579f 100644 --- a/runtime/doc/ft_rust.txt +++ b/runtime/doc/ft_rust.txt @@ -467,8 +467,8 @@ rust.vim Debugging ~ register. :RustInfoToFile [filename] *:RustInfoToFile* - Saves debugging info of the Vim Rust plugin to the the given - file, overwritting it. + Saves debugging info of the Vim Rust plugin to the given file, + overwriting it. ============================================================================== MAPPINGS *rust-mappings* diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 0fa5ffd598..ce2ec36ca3 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1093,11 +1093,11 @@ To get basic "autocompletion" without installing a plugin, try this script: >lua vim.api.nvim_create_autocmd("InsertCharPre", { buffer = vim.api.nvim_get_current_buf(), callback = function() - if vim.fn.pumvisible() == 1 then + if vim.fn.pumvisible() == 1 or vim.fn.state("m") == "m" then return end local char = vim.v.char - if vim.tbl_contains(triggers, char) then + if vim.list_contains(triggers, char) then local key = vim.keycode("<C-x><C-n>") vim.api.nvim_feedkeys(key, "m", false) end diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 975aff6bf6..299c18ac2e 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -536,7 +536,7 @@ CTRL-O in Insert mode you get a beep but you are still in Insert mode, type Visual *2 ^G c C -- : -- Select *5 ^O ^G *6 -- -- -- Insert <Esc> -- -- <Insert> -- -- - Replace <Esc> -- -- <Insert> -- -- + Replace <Esc> -- -- <Insert> -- -- Command-line `*3` -- -- :start -- -- Ex :vi -- -- -- -- -- diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 5103cc223f..23902f8bc3 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1430,7 +1430,7 @@ display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* Display the lenses using virtual text Parameters: ~ - • {lenses} (table) of lenses to display (`CodeLens[] | null`) + • {lenses} lsp.CodeLens[]|nil lenses to display • {bufnr} (integer) • {client_id} (integer) @@ -1442,7 +1442,7 @@ get({bufnr}) *vim.lsp.codelens.get()* buffer. Return: ~ - (table) (`CodeLens[]`) + lsp.CodeLens[] *vim.lsp.codelens.on_codelens()* on_codelens({err}, {result}, {ctx}, {_}) @@ -1464,7 +1464,7 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* Store lenses for a specific buffer and client Parameters: ~ - • {lenses} (table) of lenses to store (`CodeLens[] | null`) + • {lenses} lsp.CodeLens[]|nil lenses to store • {bufnr} (integer) • {client_id} (integer) @@ -1678,20 +1678,24 @@ convert_input_to_markdown_lines({input}, {contents}) window for `textDocument/hover`, for parsing the result of `textDocument/signatureHelp`, and potentially others. + Note that if the input is of type `MarkupContent` and its kind is + `plaintext`, then the corresponding value is returned without further + modifications. + Parameters: ~ • {input} (`MarkedString` | `MarkedString[]` | `MarkupContent`) • {contents} (table|nil) List of strings to extend with converted lines. Defaults to {}. Return: ~ - (table) {contents} extended with lines of converted markdown. + string[] extended with lines of converted markdown. See also: ~ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover *vim.lsp.util.convert_signature_help_to_markdown_lines()* convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers}) - Converts `textDocument/SignatureHelp` response to markdown lines. + Converts `textDocument/signatureHelp` response to markdown lines. Parameters: ~ • {signature_help} (table) Response of `textDocument/SignatureHelp` @@ -1908,10 +1912,6 @@ open_floating_preview({contents}, {syntax}, {opts}) height when wrap is enabled • max_width: (integer) maximal width of floating window • max_height: (integer) maximal height of floating window - • pad_top: (integer) number of lines to pad contents at - top - • pad_bottom: (integer) number of lines to pad contents at - bottom • focus_id: (string) if a popup with this id is opened, then focus it • close_events: (table) list of events that closes the @@ -2005,8 +2005,6 @@ stylize_markdown({bufnr}, {contents}, {opts}) • wrap_at character to wrap at for computing height • max_width maximal width of floating window • max_height maximal height of floating window - • pad_top number of lines to pad contents at top - • pad_bottom number of lines to pad contents at bottom • separator insert separator after code block Return: ~ @@ -2035,15 +2033,6 @@ text_document_completion_list_to_complete_items({result}, {prefix}) See also: ~ • complete-items -trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()* - Removes empty lines from the beginning and end. - - Parameters: ~ - • {lines} (table) list of lines to trim - - Return: ~ - (table) trimmed list of lines - *vim.lsp.util.try_trim_markdown_code_blocks()* try_trim_markdown_code_blocks({lines}) Accepts markdown lines and tries to reduce them to a filetype if they diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6cfec45523..7e888a2375 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -956,12 +956,12 @@ vim.rpcrequest({channel}, {method}, {args}, {...}) *vim.rpcrequest()* • {args} any[]|nil • {...} any|nil -vim.schedule({callback}) *vim.schedule()* - Schedules {callback} to be invoked soon by the main event-loop. Useful to - avoid |textlock| or other temporary restrictions. +vim.schedule({fn}) *vim.schedule()* + Schedules {fn} to be invoked soon by the main event-loop. Useful to avoid + |textlock| or other temporary restrictions. Parameters: ~ - • {callback} fun() + • {fn} (function) vim.str_byteindex({str}, {index}, {use_utf16}) *vim.str_byteindex()* Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not @@ -1680,7 +1680,7 @@ vim.print({...}) *vim.print()* "Pretty prints" the given arguments and returns them unmodified. Example: >lua - local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) + local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' })) < Return: ~ @@ -1714,11 +1714,20 @@ vim.region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) `endcol` is exclusive, and whole lines are returned as `{startcol,endcol} = {0,-1}`. -vim.schedule_wrap({cb}) *vim.schedule_wrap()* - Defers callback `cb` until the Nvim API is safe to call. +vim.schedule_wrap({fn}) *vim.schedule_wrap()* + Returns a function which calls {fn} via |vim.schedule()|. + + The returned function passes all arguments to {fn}. + + Example: >lua + function notify_readable(_err, readable) + vim.notify("readable? " .. tostring(readable)) + end + vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable)) +< Parameters: ~ - • {cb} (function) + • {fn} (function) Return: ~ (function) @@ -1880,25 +1889,22 @@ vim.deepcopy({orig}) *vim.deepcopy()* Return: ~ (table) Table of copied keys and (nested) values. -vim.defaulttable({create}) *vim.defaulttable()* - Creates a table whose members are automatically created when accessed, if - they don't already exist. +vim.defaulttable({createfn}) *vim.defaulttable()* + Creates a table whose missing keys are provided by {createfn} (like + Python's "defaultdict"). - They mimic defaultdict in python. - - If {create} is `nil`, this will create a defaulttable whose constructor - function is this function, effectively allowing to create nested tables on - the fly: >lua + If {createfn} is `nil` it defaults to defaulttable() itself, so accessing + nested keys creates nested tables: >lua local a = vim.defaulttable() a.b.c = 1 < Parameters: ~ - • {create} function?(key:any):any The function called to create a - missing value. + • {createfn} function?(key:any):any Provides the value for a missing + `key`. Return: ~ - (table) Empty table with metamethod + (table) Empty table with `__index` metamethod. vim.endswith({s}, {suffix}) *vim.endswith()* Tests if `s` ends with `suffix`. @@ -2061,13 +2067,13 @@ vim.Ringbuf:push({item}) *Ringbuf:push()* • {item} any vim.spairs({t}) *vim.spairs()* - Enumerate a table sorted by its keys. + Enumerates key-value pairs of a table, ordered by key. Parameters: ~ • {t} (table) Dict-like table Return: ~ - (function) iterator over sorted keys and their values + (function) |for-in| iterator over sorted keys and their values See also: ~ • Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua @@ -2232,12 +2238,14 @@ vim.tbl_get({o}, {...}) *vim.tbl_get()* any Nested value indexed by key (if it exists), else nil vim.tbl_isarray({t}) *vim.tbl_isarray()* - Tests if a Lua table can be treated as an array (a table indexed by - integers). + Tests if `t` is an "array": a table indexed only by integers (potentially non-contiguous). + + If the indexes start from 1 and are contiguous then the array is also a + list. |vim.tbl_islist()| - Empty table `{}` is assumed to be an array, unless it was created by - |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, - for example from |rpcrequest()| or |vim.fn|. + Empty table `{}` is an array, unless it was created by |vim.empty_dict()| + or returned as a dict-like |API| or Vimscript result, for example from + |rpcrequest()| or |vim.fn|. Parameters: ~ • {t} (table) @@ -2245,6 +2253,9 @@ vim.tbl_isarray({t}) *vim.tbl_isarray()* Return: ~ (boolean) `true` if array-like table, else `false`. + See also: ~ + • https://github.com/openresty/luajit2#tableisarray + vim.tbl_isempty({t}) *vim.tbl_isempty()* Checks if a table is empty. @@ -2258,12 +2269,12 @@ vim.tbl_isempty({t}) *vim.tbl_isempty()* • https://github.com/premake/premake-core/blob/master/src/base/table.lua vim.tbl_islist({t}) *vim.tbl_islist()* - Tests if a Lua table can be treated as a list (a table indexed by - consecutive integers starting from 1). + Tests if `t` is a "list": a table indexed only by contiguous integers starting from 1 (what |lua-length| calls a "regular + array"). - Empty table `{}` is assumed to be an list, unless it was created by - |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, - for example from |rpcrequest()| or |vim.fn|. + Empty table `{}` is a list, unless it was created by |vim.empty_dict()| or + returned as a dict-like |API| or Vimscript result, for example from + |rpcrequest()| or |vim.fn|. Parameters: ~ • {t} (table) @@ -2271,6 +2282,9 @@ vim.tbl_islist({t}) *vim.tbl_islist()* Return: ~ (boolean) `true` if list-like table, else `false`. + See also: ~ + • |vim.tbl_isarray()| + vim.tbl_keys({t}) *vim.tbl_keys()* Return a list of all keys used in a table. However, the order of the return table of keys is not guaranteed. @@ -2738,7 +2752,7 @@ vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()* Parameters: ~ • {opts} (table|nil) A table of optional arguments: - • "buffer": (number|boolean) Remove a mapping from the given + • "buffer": (integer|boolean) Remove a mapping from the given buffer. When `0` or `true`, use the current buffer. See also: ~ @@ -2772,7 +2786,7 @@ vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* • "noremap": inverse of "remap" (see below). • Also accepts: - • "buffer": (number|boolean) Creates buffer-local mapping, + • "buffer": (integer|boolean) Creates buffer-local mapping, `0` or `true` for current buffer. • "remap": (boolean) Make the mapping recursive. Inverse of "noremap". Defaults to `false`. diff --git a/runtime/doc/luaref.txt b/runtime/doc/luaref.txt index 341f620a8d..467b5760cf 100644 --- a/runtime/doc/luaref.txt +++ b/runtime/doc/luaref.txt @@ -568,8 +568,7 @@ Binary operators comprise arithmetic operators (see |lua-arithmetic|), relational operators (see |lua-relational|), logical operators (see |lua-logicalop|), and the concatenation operator (see |lua-concat|). Unary operators comprise the unary minus (see |lua-arithmetic|), the unary -`not` (see |lua-logicalop|), and the unary length operator (see -|lua-length|). +`not` (see |lua-logicalop|), and the unary length operator (see |lua-length|). Both function calls and vararg expressions may result in multiple values. If the expression is used as a statement (see |lua-funcstatement|) @@ -690,7 +689,7 @@ according to the rules mentioned in |lua-coercion|. Otherwise, the "concat" metamethod is called (see |lua-metatable|). ------------------------------------------------------------------------------ -2.5.5 The Length Operator *lua-length* +2.5.5 The Length Operator *lua-#* *lua-length* The length operator is denoted by the unary operator `#`. The length of a string is its number of bytes (that is, the usual meaning of string length @@ -3505,7 +3504,7 @@ luaL_where *luaL_where()* This function is used to build a prefix for error messages. ============================================================================== -5 STANDARD LIBRARIES *lua-Lib* +5 STANDARD LIBRARIES *lua-lib* The standard libraries provide useful functions that are implemented directly through the C API. Some of these functions provide essential services to the @@ -3653,8 +3652,8 @@ next({table} [, {index}]) *next()* argument is absent, then it is interpreted as `nil`. In particular, you can use `next(t)` to check whether a table is empty. - The order in which the indices are enumerated is not specified, `even - for` `numeric indices`. (To traverse a table in numeric order, use a + The order in which the indices are enumerated is not specified, even + for numeric indices. (To traverse a table in numeric order, use a numerical `for` or the |ipairs()| function.) The behavior of `next` is `undefined` if, during the traversal, you @@ -3759,7 +3758,7 @@ unpack({list} [, {i} [, {j}]]) *unpack()* < except that the above code can be written only for a fixed number of elements. By default, {i} is 1 and {j} is the length of the list, as - defined by the length operator(see |lua-length|). + defined by the length operator (see |lua-length|). _VERSION *_VERSION* A global variable (not a function) that holds a string containing the @@ -4299,12 +4298,11 @@ table.remove({table} [, {pos}]) *table.remove()* table.sort({table} [, {comp}]) *table.sort()* Sorts table elements in a given order, `in-place`, from `table[1]` to - `table[n]`, where `n` is the length of the table (see - |lua-length|). If {comp} is given, then it must be a function - that receives two table elements, and returns true when the first is - less than the second (so that `not comp(a[i+1],a[i])` will be true - after the sort). If {comp} is not given, then the standard Lua - operator `<` is used instead. + `table[n]`, where `n` is the length of the table (see |lua-length|). + If {comp} is given, then it must be a function that receives two table + elements, and returns true when the first is less than the second (so + that `not comp(a[i+1],a[i])` will be true after the sort). If {comp} + is not given, then the standard Lua operator `<` is used instead. The sort algorithm is `not` stable, that is, elements considered equal by the given order may have their relative positions changed by the sort. diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 05244cde91..5e18595d22 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -660,6 +660,7 @@ i` *v_i`* *i`* Special case: With a count of 2 the quotes are included, but no extra white space as with a"/a'/a`. + *o_object-select* When used after an operator: For non-block objects: For the "a" commands: The operator applies to the object and the white @@ -675,6 +676,7 @@ For a block object: the surrounding braces are excluded. For the "a" commands, the braces are included. + *v_object-select* When used in Visual mode: When start and end of the Visual area are the same (just after typing "v"): One object is selected, the same as for using an operator. @@ -1048,14 +1050,14 @@ can go to cursor positions before older jumps, and back again. Thus you can move up and down the list. There is a separate jump list for each window. The maximum number of entries is fixed at 100. -For example, after three jump commands you have this jump list: - - jump line col file/text ~ - 3 1 0 some text ~ - 2 70 0 another line ~ - 1 1154 23 end. ~ - > ~ +For example, after three jump commands you have this jump list: > + jump line col file/text + 3 1 0 some text + 2 70 0 another line + 1 1154 23 end. + > +< The "file/text" column shows the file name, or the text at the jump if it is in the current file (an indent is removed and a long line is truncated to fit in the window). @@ -1064,14 +1066,14 @@ The marker ">" indicates the current position in the jumplist. It may not be shown when filtering the |:jumps| command using |:filter| You are currently in line 1167. If you then use the CTRL-O command, the -cursor is put in line 1154. This results in: - - jump line col file/text ~ - 2 1 0 some text ~ - 1 70 0 another line ~ - > 0 1154 23 end. ~ - 1 1167 0 foo bar ~ +cursor is put in line 1154. This results in: > + jump line col file/text + 2 1 0 some text + 1 70 0 another line + > 0 1154 23 end. + 1 1167 0 foo bar +< The pointer will be set at the last used jump position. The next CTRL-O command will use the entry above it, the next CTRL-I command will use the entry below it. If the pointer is below the last entry, this indicates that @@ -1095,15 +1097,15 @@ command. You can explicitly add a jump by setting the ' mark with "m'". Note that calling setpos() does not do this. After the CTRL-O command that got you into line 1154 you could give another -jump command (e.g., "G"). The jump list would then become: - - jump line col file/text ~ - 4 1 0 some text ~ - 3 70 0 another line ~ - 2 1167 0 foo bar ~ - 1 1154 23 end. ~ - > ~ - +jump command (e.g., "G"). The jump list would then become: > + + jump line col file/text + 4 1 0 some text + 3 70 0 another line + 2 1167 0 foo bar + 1 1154 23 end. + > +< The line numbers will be adjusted for deleted and inserted lines. This fails if you stop editing a file without writing, like with ":n!". @@ -1113,60 +1115,44 @@ If you have included the ' item in the 'shada' option the jumplist will be stored in the ShaDa file and restored when starting Vim. *jumplist-stack* -When jumpoptions includes "stack", the jumplist behaves like the history in a -web browser and like the tag stack. When jumping to a new location from the -middle of the jumplist, the locations after the current position will be -discarded. - -This behavior corresponds to the following situation in a web browser. -Navigate to first.com, second.com, third.com, fourth.com and then fifth.com. -Then navigate backwards twice so that third.com is displayed. At that point, -the history is: -- first.com -- second.com -- third.com <-- -- fourth.com -- fifth.com - -Finally, navigate to a different webpage, new.com. The history is -- first.com -- second.com -- third.com -- new.com <-- - -When the jumpoptions includes "stack", this is the behavior of Nvim as well. -That is, given a jumplist like the following in which CTRL-O has been used to -move back three times to location X - - jump line col file/text - 2 1260 8 src/nvim/mark.c <-- location X-2 - 1 685 0 src/nvim/option_defs.h <-- location X-1 -> 0 462 36 src/nvim/option_defs.h <-- location X - 1 479 39 src/nvim/option_defs.h - 2 213 2 src/nvim/mark.c - 3 181 0 src/nvim/mark.c - +When 'jumpoptions' option includes "stack", the jumplist behaves like the tag +stack. When jumping to a new location from the middle of the jumplist, the +locations after the current position will be discarded. With this option set +you can move through a tree of jump locations. When going back up a branch and +then down another branch, CTRL-O still takes you further up the tree. + +Given a jumplist like the following in which CTRL-O has been used to move back +three times to location X: > + + jump line col file/text + 2 1260 8 mark.c <-- location X-2 + 1 685 0 eval.c <-- location X-1 + > 0 462 36 eval.c <-- location X + 1 479 39 eval.c + 2 213 2 mark.c + 3 181 0 mark.c +< jumping to (new) location Y results in the locations after the current -locations being removed: - - jump line col file/text - 3 1260 8 src/nvim/mark.c - 2 685 0 src/nvim/option_defs.h - 1 462 36 src/nvim/option_defs.h <-- location X -> +locations being removed: > + jump line col file/text + 3 1260 8 mark.c <-- location X-2 + 2 685 0 eval.c <-- location X-1 + 1 462 36 eval.c <-- location X + > +< Then, when yet another location Z is jumped to, the new location Y appears directly after location X in the jumplist and location X remains in the same -position relative to the locations (X-1, X-2, etc., ...) that had been before it -prior to the original jump from X to Y: - - jump line col file/text - 4 1260 8 src/nvim/mark.c <-- location X-2 - 3 685 0 src/nvim/option_defs.h <-- location X-1 - 2 462 36 src/nvim/option_defs.h <-- location X - 1 100 0 src/nvim/option_defs.h <-- location Y -> - +position relative to the locations (X-1, X-2, etc., ...) that had been before +it prior to the original jump from X to Y: > + + jump line col file/text + 4 1260 8 mark.c <-- location X-2 + 3 685 0 eval.c <-- location X-1 + 2 462 36 eval.c <-- location X + 1 100 0 buffer.c <-- location Y + > +< CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664* When making a change the cursor position is remembered. One position is diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 7c971097fb..05a2d35f9a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -232,6 +232,11 @@ The following changes to existing APIs or features add new behavior. In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to return such ranges even if they started before the specified position. +• LSP hover and signature help now use Treesitter for highlighting of Markdown + content. + Note that syntax highlighting of code examples requires a matching parser + and may be affected by custom queries. + ============================================================================== REMOVED FEATURES *news-removed* @@ -242,6 +247,12 @@ The following deprecated functions or APIs were removed. • Support for legacy treesitter injection queries is removed. +• Removed 'shortmess' flags: + - |shm-f|. Always uses "(3 of 5)", never "(file 3 of 5)" + - |shm-i|. Always use "[noeol]". + - |shm-x|. Always use "[dos]", "[unix]" and "[mac]" + - |shm-n|. Always use "[New]". + ============================================================================== DEPRECATIONS *news-deprecations* @@ -267,6 +278,7 @@ release. - |vim.lsp.util.get_progress_messages()| Use |vim.lsp.status()| instead. - |vim.lsp.get_active_clients()| Use |vim.lsp.get_clients()| instead. - |vim.lsp.for_each_buffer_client()| Use |vim.lsp.get_clients()| instead. + - |vim.lsp.util.trim_empty_lines()| Use |vim.split()| with `trimempty` instead. • `vim.loop` has been renamed to `vim.uv`. diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index d0d535566d..0cfeb3dcb7 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -482,7 +482,7 @@ The function will be called with the list of arguments so far, and a second argument that is the name of the pty. *gdb-version* Only debuggers fully compatible with gdb will work. Vim uses the GDB/MI -interface. The "new-ui" command requires gdb version 7.12 or later. if you +interface. The "new-ui" command requires gdb version 7.12 or later. If you get this error: Undefined command: "new-ui". Try "help".~ Then your gdb is too old. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e1518c58bb..0bbbc95141 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2465,8 +2465,8 @@ A jump table for the options with a short description can be found at |Q_op|. and the value of that item: item default Used for ~ - stl ' ' or '^' statusline of the current window - stlnc ' ' or '=' statusline of the non-current windows + stl ' ' statusline of the current window + stlnc ' ' statusline of the non-current windows wbr ' ' window bar horiz '─' or '-' horizontal separators |:split| horizup '┴' or '-' upwards facing horizontal separator @@ -2485,9 +2485,7 @@ A jump table for the options with a short description can be found at |Q_op|. eob '~' empty lines at the end of a buffer lastline '@' 'display' contains lastline/truncate - Any one that is omitted will fall back to the default. For "stl" and - "stlnc" the space will be used when there is highlighting, '^' or '=' - otherwise. + Any one that is omitted will fall back to the default. Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and "verthoriz" are only used when 'laststatus' is 3, since only vertical @@ -3235,9 +3233,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'ignorecase'* *'ic'* *'noignorecase'* *'noic'* 'ignorecase' 'ic' boolean (default off) global - Ignore case in search patterns. Also used when searching in the tags - file. - Also see 'smartcase' and 'tagcase'. + Ignore case in search patterns, completion, and when searching the tags file. + See also 'smartcase' and 'tagcase'. Can be overruled by using "\c" or "\C" in the pattern, see |/ignorecase|. @@ -3570,12 +3567,11 @@ A jump table for the options with a short description can be found at |Q_op|. 'jumpoptions' 'jop' string (default "") global List of words that change the behavior of the |jumplist|. - stack Make the jumplist behave like the tagstack or like a - web browser. Relative location of entries in the - jumplist is preserved at the cost of discarding - subsequent entries when navigating backwards in the - jumplist and then jumping to a location. - |jumplist-stack| + stack Make the jumplist behave like the tagstack. + Relative location of entries in the jumplist is + preserved at the cost of discarding subsequent entries + when navigating backwards in the jumplist and then + jumping to a location. |jumplist-stack| view When moving through the jumplist, |changelist|, |alternate-file| or using |mark-motions| try to @@ -5387,23 +5383,17 @@ A jump table for the options with a short description can be found at |Q_op|. function to get the effective shiftwidth value. *'shortmess'* *'shm'* *E1336* -'shortmess' 'shm' string (default "filnxtToOCF") +'shortmess' 'shm' string (default "ltToOCF") global This option helps to avoid all the |hit-enter| prompts caused by file messages, for example with CTRL-G, and to avoid some other messages. It is a list of flags: flag meaning when present ~ - f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f* - i use "[noeol]" instead of "[Incomplete last line]" *shm-i* l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* m use "[+]" instead of "[Modified]" *shm-m* - n use "[New]" instead of "[New File]" *shm-n* r use "[RO]" instead of "[readonly]" *shm-r* w use "[w]" instead of "written" for file write message *shm-w* and "[a]" instead of "appended" for ':w >> file' command - x use "[dos]" instead of "[dos format]", "[unix]" *shm-x* - instead of "[unix format]" and "[mac]" instead of "[mac - format]" a all of the above abbreviations *shm-a* o overwrite message for writing a file with subsequent *shm-o* diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 5f6e932693..17d5b4668d 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1362,9 +1362,9 @@ paths. *base-directories* *xdg* The "base" (root) directories conform to the XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html -The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, and $XDG_STATE_HOME -environment variables are used if defined, else default values (listed below) -are used. +The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, $XDG_STATE_HOME, +$XDG_CACHE_HOME, $XDG_CONFIG_DIRS and $XDG_DATA_DIRS environment variables +are used if defined, else default values (listed below) are used. CONFIG DIRECTORY (DEFAULT) ~ *$XDG_CONFIG_HOME* Nvim: stdpath("config") @@ -1386,9 +1386,32 @@ STATE DIRECTORY (DEFAULT) ~ Unix: ~/.local/state ~/.local/state/nvim Windows: ~/AppData/Local ~/AppData/Local/nvim-data +CACHE DIRECTORY (DEFAULT) ~ + *$XDG_CACHE_HOME* Nvim: stdpath("cache") + Unix: ~/.cache ~/.cache/nvim + Windows: ~/AppData/Local/Temp ~/AppData/Local/Temp/nvim-data + +LOG FILE (DEFAULT) ~ + `$NVIM_LOG_FILE` Nvim: stdpath("log") + Unix: ~/.local/state/nvim ~/.local/state/nvim/log + Windows: ~/AppData/Local/nvim-data ~/AppData/Local/nvim-data/log + +ADDITIONAL CONFIGS DIRECTORY (DEFAULT) ~ + *$XDG_CONFIG_DIRS* Nvim: stdpath("config_dirs") + Unix: /etc/xdg/ /etc/xdg/nvim + Windows: Not applicable Not applicable + +ADDITIONAL DATA DIRECTORY (DEFAULT) ~ + *$XDG_DATA_DIRS* Nvim: stdpath("data_dirs") + Unix: /usr/local/share /usr/local/share/nvim + /usr/share /usr/share/nvim + Windows: Not applicable Not applicable + Note: Throughout the help pages these defaults are used as placeholders, e.g. "~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config". +Note: The log file directory is controlled by `$XDG_STATE_HOME`. + NVIM_APPNAME *$NVIM_APPNAME* The standard directories can be further configured by the `$NVIM_APPNAME` environment variable. This variable controls the sub-directory that Nvim will @@ -1408,8 +1431,8 @@ LOG FILE *log* *$NVIM_LOG_FILE* *E5430* Besides 'debug' and 'verbose', Nvim keeps a general log file for internal debugging, plugins and RPC clients. > :echo $NVIM_LOG_FILE -By default, the file is located at stdpath("log")/log unless that path -is inaccessible or if $NVIM_LOG_FILE was set before |startup|. +By default, the file is located at stdpath("log")/log ($XDG_STATE_HOME/nvim/log) +unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|. vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index c15b41f9aa..e1053b54f1 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5322,8 +5322,6 @@ SpellRare Word that is recognized by the spellchecker as one that is StatusLine Status line of current window. *hl-StatusLineNC* StatusLineNC Status lines of not-current windows. - Note: If this is equal to "StatusLine", Vim will use "^^^" in - the status line of the current window. *hl-TabLine* TabLine Tab pages line, not active tab page label. *hl-TabLineFill* diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index e1e280c4db..34971c7acf 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -854,12 +854,14 @@ add_predicate({name}, {handler}, {force}) • {force} (boolean|nil) edit({lang}) *vim.treesitter.query.edit()* - Open a window for live editing of a treesitter query. + Opens a live editor to query the buffer you started from. - Can also be shown with `:EditQuery`. *:EditQuery* + Can also be shown with *:EditQuery*. - Note that the editor opens a scratch buffer, and so queries aren't - persisted on disk. + If you move the cursor to a capture name ("@foo"), text matching the + capture is highlighted in the source buffer. The query editor is a scratch + buffer, use `:write` to save it. You can find example queries at + `$VIMRUNTIME/queries/`. Parameters: ~ • {lang} (string|nil) language to open the query editor for. If @@ -1101,10 +1103,12 @@ LanguageTree:for_each_tree({fn}) *LanguageTree:for_each_tree()* • {fn} fun(tree: TSTree, ltree: LanguageTree) LanguageTree:included_regions() *LanguageTree:included_regions()* - Gets the set of included regions + Gets the set of included regions managed by this LanguageTree . This can be different from the regions set by injection query, because a + partial |LanguageTree:parse()| drops the regions outside the requested + range. Return: ~ - Range6[][] + table<integer, Range6[]> LanguageTree:invalidate({reload}) *LanguageTree:invalidate()* Invalidates this parser and all its children @@ -1113,10 +1117,12 @@ LanguageTree:invalidate({reload}) *LanguageTree:invalidate()* • {reload} (boolean|nil) LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()* - Determines whether this tree is valid. If the tree is invalid, call `parse()` . This will return the updated tree. + Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest state of the + source. If invalid, user should call |LanguageTree:parse()|. Parameters: ~ - • {exclude_children} (boolean|nil) + • {exclude_children} (boolean|nil) whether to ignore the validity of + children (default `false`) Return: ~ (boolean) @@ -1165,7 +1171,7 @@ LanguageTree:parse({range}) *LanguageTree:parse()* injections). Return: ~ - TSTree[] + table<integer, TSTree> *LanguageTree:register_cbs()* LanguageTree:register_cbs({cbs}, {recursive}) @@ -1207,7 +1213,12 @@ LanguageTree:tree_for_range({range}, {opts}) TSTree|nil LanguageTree:trees() *LanguageTree:trees()* - Returns all trees this language tree contains. Does not include child - languages. + Returns all trees of the regions parsed by this parser. Does not include + child languages. The result is list-like if + • this LanguageTree is the root, in which case the result is empty or a singleton list; or + • the root LanguageTree is fully parsed. + + Return: ~ + table<integer, TSTree> vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index 259bb41200..1fc612de26 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -40,7 +40,7 @@ blank window. This is what your screen will look like: |~ | |~ | |~ | - |"file.txt" [New file] | + |"file.txt" [New] | +---------------------------------------+ ('#' is the cursor position.) < diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index ab5e795c16..e636746616 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -45,7 +45,7 @@ Defaults *nvim-defaults* - 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created - 'display' defaults to "lastline" - 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding) -- 'fillchars' defaults (in effect) to "vert:│,fold:·,sep:│" +- 'fillchars' defaults (in effect) to "vert:│,fold:·,foldsep:│" - 'formatoptions' defaults to "tcqj" - 'fsync' is disabled - 'hidden' is enabled @@ -534,6 +534,10 @@ Vimscript compatibility: `shell_error` does not alias to |v:shell_error| `this_session` does not alias to |v:this_session| +UI/Display: + 'statusline' always uses the "stl" and "stlnc" fields of 'fillchars', even + if they are the same and |hl-StatusLine| and |hl-StatusLineNC| are equal. + Working directory (Vim implemented some of these after Nvim): - |DirChanged| and |DirChangedPre| can be triggered when switching to another window or tab. @@ -727,6 +731,7 @@ Options: Everything is allowed in 'exrc' files since they must be explicitly marked trusted. *'shelltype'* + 'shortmess' flags: *shm-f* *shm-n* *shm-x* *shm-i* (behave like always on) *'shortname'* *'sn'* *'noshortname'* *'nosn'* *'swapsync'* *'sws'* *'termencoding'* *'tenc'* (Vim 7.4.852 also removed this for Windows) diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index 6ca486e8cf..0d1ea937c0 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -169,6 +169,7 @@ If you want to highlight exactly the same area as the last time, you can use CTRL-C In Visual mode: Stop Visual mode. When insert mode is pending (the mode message shows "-- (insert) VISUAL --"), it is also stopped. + On MS-Windows, you may need to press CTRL-Break. ============================================================================== 3. Changing the Visual area *visual-change* diff --git a/runtime/ftplugin/d.lua b/runtime/ftplugin/d.lua new file mode 100644 index 0000000000..b4e68148f5 --- /dev/null +++ b/runtime/ftplugin/d.lua @@ -0,0 +1 @@ +vim.bo.commentstring = '/*%s*/' diff --git a/runtime/ftplugin/kotlin.vim b/runtime/ftplugin/kotlin.vim new file mode 100644 index 0000000000..b21de603ea --- /dev/null +++ b/runtime/ftplugin/kotlin.vim @@ -0,0 +1,33 @@ +" Vim filetype plugin file +" Language: Kotlin +" Maintainer: Alexander Udalov +" URL: https://github.com/udalov/kotlin-vim +" Last Change: 7 November 2021 +" 2023 Sep 17 by Vim Project (browsefilter) + +if exists('b:did_ftplugin') | finish | endif +let b:did_ftplugin = 1 + +let s:save_cpo = &cpo +set cpo&vim + +setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:// +setlocal commentstring=//\ %s + +setlocal formatoptions-=t formatoptions+=croqnl +silent! setlocal formatoptions+=j + +setlocal includeexpr=substitute(v:fname,'\\.','/','g') +setlocal suffixesadd=.kt + +let b:undo_ftplugin = "setlocal comments< commentstring< ". + \ "formatoptions< includeexpr< suffixesadd<" + +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter = "Kotlin Source Files (*.kt, *kts)\t*.kt;*.kts\n" . + \ "All Files (*.*)\t*.*\n" + let b:undo_ftplugin .= " | unlet! b:browsefilter" +endif + +let &cpo = s:save_cpo +unlet s:save_cpo diff --git a/runtime/ftplugin/sh.vim b/runtime/ftplugin/sh.vim index 4409f3f90c..c227838d18 100644 --- a/runtime/ftplugin/sh.vim +++ b/runtime/ftplugin/sh.vim @@ -39,8 +39,7 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") let b:undo_ftplugin ..= " | unlet! b:browsefilter" endif -if (exists("b:is_bash") && (b:is_bash == 1)) || - \ (exists("b:is_sh") && (b:is_sh == 1)) +if (exists("b:is_bash") && (b:is_bash == 1)) if !has("gui_running") && executable("less") command! -buffer -nargs=1 Help silent exe '!bash -c "{ help "<args>" 2>/dev/null || man "<args>"; } | LESS= less"' | redraw! elseif has('terminal') diff --git a/runtime/indent/kotlin.vim b/runtime/indent/kotlin.vim new file mode 100644 index 0000000000..590a5074d1 --- /dev/null +++ b/runtime/indent/kotlin.vim @@ -0,0 +1,60 @@ +" Vim indent file +" Language: Kotlin +" Maintainer: Alexander Udalov +" URL: https://github.com/udalov/kotlin-vim +" Last Change: 7 November 2021 +" 2023 Sep 17 by Vim Project (undo_indent) + +if exists('b:did_indent') + finish +endif +let b:did_indent = 1 + +setlocal cinoptions& cinoptions+=j1,L0 +setlocal indentexpr=GetKotlinIndent() +setlocal indentkeys=0},0),!^F,o,O,e,<CR> +setlocal autoindent " TODO ? + +let b:undo_indent = "setlocal autoindent< cinoptions< indentexpr< indentkeys<" + +" TODO teach it to count bracket balance, etc. +function! GetKotlinIndent() + if v:lnum == 0 + return 0 + endif + + let prev_num = prevnonblank(v:lnum - 1) + let prev = getline(prev_num) + let prev_indent = indent(prev_num) + let cur = getline(v:lnum) + + if cur =~ '^\s*\*' + return cindent(v:lnum) + endif + + if prev =~ '^\s*\*/' + let st = prev + while st > 1 + if getline(st) =~ '^\s*/\*' + break + endif + let st = st - 1 + endwhile + return indent(st) + endif + + let prev_open_paren = prev =~ '^.*(\s*$' + let cur_close_paren = cur =~ '^\s*).*$' + let prev_open_brace = prev =~ '^.*\({\|->\)\s*$' + let cur_close_brace = cur =~ '^\s*}.*$' + + if prev_open_paren && !cur_close_paren || prev_open_brace && !cur_close_brace + return prev_indent + shiftwidth() + endif + + if cur_close_paren && !prev_open_paren || cur_close_brace && !prev_open_brace + return prev_indent - shiftwidth() + endif + + return prev_indent +endfunction diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua index 7ccb082a40..6b6370fa19 100644 --- a/runtime/lua/nvim/health.lua +++ b/runtime/lua/nvim/health.lua @@ -54,15 +54,19 @@ local function check_config() health.start('Configuration') local ok = true - local vimrc = ( - empty(vim.env.MYVIMRC) and vim.fn.stdpath('config') .. '/init.vim' or vim.env.MYVIMRC - ) - if not filereadable(vimrc) then + local init_lua = vim.fn.stdpath('config') .. '/init.lua' + local init_vim = vim.fn.stdpath('config') .. '/init.vim' + local vimrc = empty(vim.env.MYVIMRC) and init_lua or vim.env.MYVIMRC + + if not filereadable(vimrc) and not filereadable(init_vim) then ok = false local has_vim = filereadable(vim.fn.expand('~/.vimrc')) health.warn( - (-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc, - { has_vim and ':help nvim-from-vim' or ':help init.vim' } + ('%s user config file: %s'):format( + -1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable', + vimrc + ), + { has_vim and ':help nvim-from-vim' or ':help config' } ) end diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 0215cae0cb..7f09fc8038 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -29,6 +29,7 @@ for k, v in pairs({ treesitter = true, filetype = true, loader = true, + func = true, F = true, lsp = true, highlight = true, @@ -315,18 +316,29 @@ do end end ---- Defers callback `cb` until the Nvim API is safe to call. +--- Returns a function which calls {fn} via |vim.schedule()|. +--- +--- The returned function passes all arguments to {fn}. +--- +--- Example: +--- +--- ```lua +--- function notify_readable(_err, readable) +--- vim.notify("readable? " .. tostring(readable)) +--- end +--- vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable)) +--- ``` --- ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| ----@param cb function +---@param fn function ---@return function -function vim.schedule_wrap(cb) +function vim.schedule_wrap(fn) return function(...) local args = vim.F.pack_len(...) vim.schedule(function() - cb(vim.F.unpack_len(args)) + fn(vim.F.unpack_len(args)) end) end end @@ -876,7 +888,7 @@ end --- Example: --- --- ```lua ---- local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) +--- local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' })) --- ``` --- --- @see |vim.inspect()| diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 5e4f390ca3..ddd0a0eb49 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -10,6 +10,7 @@ vim._watch = require('vim._watch') vim.diagnostic = require('vim.diagnostic') vim.filetype = require('vim.filetype') vim.fs = require('vim.fs') +vim.func = require('vim.func') vim.health = require('vim.health') vim.highlight = require('vim.highlight') vim.iter = require('vim.iter') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 3822e5ff5b..68ef54eb2f 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -80,6 +80,9 @@ function vim.api.nvim__id_float(flt) end function vim.api.nvim__inspect_cell(grid, row, col) end --- @private +function vim.api.nvim__invalidate_glyph_cache() end + +--- @private --- @return any[] function vim.api.nvim__runtime_inspect() end @@ -422,6 +425,7 @@ function vim.api.nvim_buf_get_number(buffer) end --- @return integer function vim.api.nvim_buf_get_offset(buffer, index) end +--- @deprecated --- @param buffer integer --- @param name string --- @return any @@ -629,6 +633,7 @@ function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end --- @param name string Buffer name function vim.api.nvim_buf_set_name(buffer, name) end +--- @deprecated --- @param buffer integer --- @param name string --- @param value any @@ -1280,10 +1285,12 @@ function vim.api.nvim_get_mode() end --- @return table<string,any> function vim.api.nvim_get_namespaces() end +--- @deprecated --- @param name string --- @return any function vim.api.nvim_get_option(name) end +--- @deprecated --- @param name string --- @return table<string,any> function vim.api.nvim_get_option_info(name) end @@ -1608,6 +1615,8 @@ function vim.api.nvim_open_term(buffer, opts) end --- • noautocmd: If true then no buffer-related autocommand --- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may --- fire from calling this function. +--- • fixed: If true when anchor is NW or SW, the float window +--- would be kept fixed even if the window would be truncated. --- @return integer function vim.api.nvim_open_win(buffer, enter, config) end @@ -1900,7 +1909,7 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end --- @param rhs string Right-hand-side `{rhs}` of the mapping. --- @param opts vim.api.keyset.keymap Optional parameters map: Accepts all `:map-arguments` as keys --- except `<buffer>`, values are booleans (default false). Also: ---- • "noremap" non-recursive mapping `:noremap` +--- • "noremap" disables `recursive_mapping`, like `:noremap` --- • "desc" human-readable description. --- • "callback" Lua function called in place of {rhs}. --- • "replace_keycodes" (boolean) When "expr" is true, replace @@ -1909,6 +1918,7 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end --- "callback" is equivalent to returning an empty string. function vim.api.nvim_set_keymap(mode, lhs, rhs, opts) end +--- @deprecated --- @param name string --- @param value any function vim.api.nvim_set_option(name, value) end @@ -2112,6 +2122,7 @@ function vim.api.nvim_win_get_height(window) end --- @return integer function vim.api.nvim_win_get_number(window) end +--- @deprecated --- @param window integer --- @param name string --- @return any @@ -2194,6 +2205,7 @@ function vim.api.nvim_win_set_height(window, height) end --- @param ns_id integer the namespace to use function vim.api.nvim_win_set_hl_ns(window, ns_id) end +--- @deprecated --- @param window integer --- @param name string --- @param value any diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 349c68f8f9..eaaa32d7b3 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -112,6 +112,7 @@ error('Cannot require a meta file') --- @field footer_pos? string --- @field style? string --- @field noautocmd? boolean +--- @field fixed? boolean --- @class vim.api.keyset.get_autocmds --- @field event? any diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 0a6dd3e151..92c23f7764 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -194,10 +194,10 @@ function vim.str_utfindex(str, index) end --- @return string|nil Converted string if conversion succeeds, `nil` otherwise. function vim.iconv(str, from, to, opts) end ---- Schedules {callback} to be invoked soon by the main event-loop. Useful +--- Schedules {fn} to be invoked soon by the main event-loop. Useful --- to avoid |textlock| or other temporary restrictions. ---- @param callback fun() -function vim.schedule(callback) end +--- @param fn function +function vim.schedule(fn) end --- Wait for {time} in milliseconds until {callback} returns `true`. --- diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua index edf905c7c6..e010086615 100644 --- a/runtime/lua/vim/_meta/json.lua +++ b/runtime/lua/vim/_meta/json.lua @@ -1,5 +1,8 @@ ---@meta +---@nodoc +vim.json = {} + -- luacheck: no unused args ---@defgroup vim.json diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua new file mode 100644 index 0000000000..42c9a6449e --- /dev/null +++ b/runtime/lua/vim/_meta/lpeg.lua @@ -0,0 +1,323 @@ +--- @meta + +-- These types were taken from https://github.com/LuaCATS/lpeg, with types being renamed to include +-- the vim namespace and with some descriptions made less verbose. + +--- *LPeg* is a new pattern-matching library for Lua, based on [Parsing Expression Grammars](https://bford.info/packrat/) (PEGs). +vim.lpeg = {} + +--- @class vim.lpeg.Pattern +--- @operator unm: vim.lpeg.Pattern +--- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern +--- @operator sub(vim.lpeg.Pattern): vim.lpeg.Pattern +--- @operator mul(vim.lpeg.Pattern): vim.lpeg.Pattern +--- @operator mul(vim.lpeg.Capture): vim.lpeg.Pattern +--- @operator div(string): vim.lpeg.Capture +--- @operator div(number): vim.lpeg.Capture +--- @operator div(table): vim.lpeg.Capture +--- @operator div(function): vim.lpeg.Capture +--- @operator pow(number): vim.lpeg.Pattern +--- @operator mod(function): nil +local Pattern = {} + +--- @alias vim.lpeg.Capture vim.lpeg.Pattern + +--- Matches the given `pattern` against the `subject` string. If the match succeeds, returns the index in the +--- subject of the first character after the match, or the captured values (if the pattern captured any value). +--- An optional numeric argument `init` makes the match start at that position in the subject string. As usual +--- in Lua libraries, a negative value counts from the end. Unlike typical pattern-matching functions, `match` +--- works only in anchored mode; that is, it tries to match the pattern with a prefix of the given subject +--- string (at position `init`), not with an arbitrary substring of the subject. So, if we want to find a +--- pattern anywhere in a string, we must either write a loop in Lua or write a pattern that +--- matches anywhere. +--- +--- Example: +--- ```lua +--- local pattern = lpeg.R("az") ^ 1 * -1 +--- assert(pattern:match("hello") == 6) +--- assert(lpeg.match(pattern, "hello") == 6) +--- assert(pattern:match("1 hello") == nil) +--- ``` +--- +--- @param pattern vim.lpeg.Pattern +--- @param subject string +--- @param init? integer +--- @return integer|vim.lpeg.Capture|nil +function vim.lpeg.match(pattern, subject, init) end + +--- Matches the given `pattern` against the `subject` string. If the match succeeds, returns the +--- index in the subject of the first character after the match, or the captured values (if the +--- pattern captured any value). An optional numeric argument `init` makes the match start at +--- that position in the subject string. As usual in Lua libraries, a negative value counts from the end. +--- Unlike typical pattern-matching functions, `match` works only in anchored mode; that is, it tries +--- to match the pattern with a prefix of the given subject string (at position `init`), not with +--- an arbitrary substring of the subject. So, if we want to find a pattern anywhere in a string, +--- we must either write a loop in Lua or write a pattern that matches anywhere. +--- +--- Example: +--- ```lua +--- local pattern = lpeg.R("az") ^ 1 * -1 +--- assert(pattern:match("hello") == 6) +--- assert(lpeg.match(pattern, "hello") == 6) +--- assert(pattern:match("1 hello") == nil) +--- ``` +--- +--- @param subject string +--- @param init? integer +--- @return integer|vim.lpeg.Capture|nil +function Pattern:match(subject, init) end + +--- Returns the string `"pattern"` if the given value is a pattern, otherwise `nil`. +--- +--- @return 'pattern'|nil +function vim.lpeg.type(value) end + +--- Returns a string with the running version of LPeg. +--- @return string +function vim.lpeg.version() end + +--- Sets a limit for the size of the backtrack stack used by LPeg to track calls and choices. +--- The default limit is `400`. Most well-written patterns need little backtrack levels and +--- therefore you seldom need to change this limit; before changing it you should try to rewrite +--- your pattern to avoid the need for extra space. Nevertheless, a few useful patterns may overflow. +--- Also, with recursive grammars, subjects with deep recursion may also need larger limits. +--- +--- @param max integer +function vim.lpeg.setmaxstack(max) end + +--- Converts the given value into a proper pattern. This following rules are applied: +--- * If the argument is a pattern, it is returned unmodified. +--- * If the argument is a string, it is translated to a pattern that matches the string literally. +--- * If the argument is a non-negative number `n`, the result is a pattern that matches exactly `n` characters. +--- * If the argument is a negative number `-n`, the result is a pattern that succeeds only if +--- the input string has less than `n` characters left: `lpeg.P(-n)` is equivalent to `-lpeg.P(n)` +--- (see the unary minus operation). +--- * If the argument is a boolean, the result is a pattern that always succeeds or always fails +--- (according to the boolean value), without consuming any input. +--- * If the argument is a table, it is interpreted as a grammar (see Grammars). +--- * If the argument is a function, returns a pattern equivalent to a match-time captureover the empty string. +--- +--- @param value vim.lpeg.Pattern|string|integer|boolean|table|function +--- @return vim.lpeg.Pattern +function vim.lpeg.P(value) end + +--- Returns a pattern that matches only if the input string at the current position is preceded by `patt`. +--- Pattern `patt` must match only strings with some fixed length, and it cannot contain captures. +--- Like the and predicate, this pattern never consumes any input, independently of success or failure. +--- +--- @param pattern vim.lpeg.Pattern +--- @return vim.lpeg.Pattern +function vim.lpeg.B(pattern) end + +--- Returns a pattern that matches any single character belonging to one of the given ranges. +--- Each `range` is a string `xy` of length 2, representing all characters with code between the codes of +--- `x` and `y` (both inclusive). As an example, the pattern `lpeg.R("09")` matches any digit, and +--- `lpeg.R("az", "AZ")` matches any ASCII letter. +--- +--- Example: +--- ```lua +--- local pattern = lpeg.R("az") ^ 1 * -1 +--- assert(pattern:match("hello") == 6) +--- ``` +--- +--- @param ... string +--- @return vim.lpeg.Pattern +function vim.lpeg.R(...) end + +--- Returns a pattern that matches any single character that appears in the given string (the `S` stands for Set). +--- As an example, the pattern `lpeg.S("+-*/")` matches any arithmetic operator. Note that, if `s` is a character +--- (that is, a string of length 1), then `lpeg.P(s)` is equivalent to `lpeg.S(s)` which is equivalent to +--- `lpeg.R(s..s)`. Note also that both `lpeg.S("")` and `lpeg.R()` are patterns that always fail. +--- +--- @param string string +--- @return vim.lpeg.Pattern +function vim.lpeg.S(string) end + +--- Creates a non-terminal (a variable) for a grammar. This operation creates a non-terminal (a variable) +--- for a grammar. The created non-terminal refers to the rule indexed by `v` in the enclosing grammar. +--- +--- Example: +--- ```lua +--- local b = lpeg.P({"(" * ((1 - lpeg.S "()") + lpeg.V(1)) ^ 0 * ")"}) +--- assert(b:match('((string))') == 11) +--- assert(b:match('(') == nil) +--- ``` +--- +--- @param v string|integer +--- @return vim.lpeg.Pattern +function vim.lpeg.V(v) end + +--- @class vim.lpeg.Locale +--- @field alnum userdata +--- @field alpha userdata +--- @field cntrl userdata +--- @field digit userdata +--- @field graph userdata +--- @field lower userdata +--- @field print userdata +--- @field punct userdata +--- @field space userdata +--- @field upper userdata +--- @field xdigit userdata + +--- Returns a table with patterns for matching some character classes according to the current locale. +--- The table has fields named `alnum`, `alpha`, `cntrl`, `digit`, `graph`, `lower`, `print`, `punct`, +--- `space`, `upper`, and `xdigit`, each one containing a correspondent pattern. Each pattern matches +--- any single character that belongs to its class. +--- If called with an argument `table`, then it creates those fields inside the given table and returns +--- that table. +--- +--- Example: +--- ```lua +--- lpeg.locale(lpeg) +--- local space = lpeg.space^0 +--- local name = lpeg.C(lpeg.alpha^1) * space +--- local sep = lpeg.S(",;") * space +--- local pair = lpeg.Cg(name * "=" * space * name) * sep^-1 +--- local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset) +--- local t = list:match("a=b, c = hi; next = pi") +--- assert(t.a == 'b') +--- assert(t.c == 'hi') +--- assert(t.next == 'pi') +--- local locale = lpeg.locale() +--- assert(type(locale.digit) == 'userdata') +--- ``` +--- +--- @param tab? table +--- @return vim.lpeg.Locale +function vim.lpeg.locale(tab) end + +--- Creates a simple capture, which captures the substring of the subject that matches `patt`. +--- The captured value is a string. If `patt` has other captures, their values are returned after this one. +--- +--- Example: +--- ```lua +--- local function split (s, sep) +--- sep = lpeg.P(sep) +--- local elem = lpeg.C((1 - sep)^0) +--- local p = elem * (sep * elem)^0 +--- return lpeg.match(p, s) +--- end +--- local a, b, c = split('a,b,c', ',') +--- assert(a == 'a') +--- assert(b == 'b') +--- assert(c == 'c') +--- ``` +--- +--- @param patt vim.lpeg.Pattern +--- @return vim.lpeg.Capture +function vim.lpeg.C(patt) end + +--- Creates an argument capture. This pattern matches the empty string and produces the value given as the +--- nth extra argument given in the call to `lpeg.match`. +--- @param n integer +--- @return vim.lpeg.Capture +function vim.lpeg.Carg(n) end + +--- Creates a back capture. This pattern matches the empty string and produces the values produced by the most recent +--- group capture named `name` (where `name` can be any Lua value). Most recent means the last complete outermost +--- group capture with the given name. A Complete capture means that the entire pattern corresponding to the capture +--- has matched. An Outermost capture means that the capture is not inside another complete capture. +--- In the same way that LPeg does not specify when it evaluates captures, it does not specify whether it reuses +--- values previously produced by the group or re-evaluates them. +--- +--- @param name any +--- @return vim.lpeg.Capture +function vim.lpeg.Cb(name) end + +--- Creates a constant capture. This pattern matches the empty string and produces all given values as its captured values. +--- +--- @param ... any +--- @return vim.lpeg.Capture +function vim.lpeg.Cc(...) end + +--- Creates a fold capture. If `patt` produces a list of captures C1 C2 ... Cn, this capture will produce the value +--- `func(...func(func(C1, C2), C3)...,Cn)`, that is, it will fold (or accumulate, or reduce) the captures from +--- `patt` using function `func`. This capture assumes that `patt` should produce at least one capture with at +--- least one value (of any type), which becomes the initial value of an accumulator. (If you need a specific +--- initial value, you may prefix a constant captureto `patt`.) For each subsequent capture, LPeg calls `func` +--- with this accumulator as the first argument and all values produced by the capture as extra arguments; +--- the first result from this call becomes the new value for the accumulator. The final value of the accumulator +--- becomes the captured value. +--- +--- Example: +--- ```lua +--- local number = lpeg.R("09") ^ 1 / tonumber +--- local list = number * ("," * number) ^ 0 +--- local function add(acc, newvalue) return acc + newvalue end +--- local sum = lpeg.Cf(list, add) +--- assert(sum:match("10,30,43") == 83) +--- ``` +--- +--- @param patt vim.lpeg.Pattern +--- @param func fun(acc, newvalue) +--- @return vim.lpeg.Capture +function vim.lpeg.Cf(patt, func) end + +--- Creates a group capture. It groups all values returned by `patt` into a single capture. +--- The group may be anonymous (if no name is given) or named with the given name (which +--- can be any non-nil Lua value). +--- +--- @param patt vim.lpeg.Pattern +--- @param name? string +--- @return vim.lpeg.Capture +function vim.lpeg.Cg(patt, name) end + +--- Creates a position capture. It matches the empty string and captures the position in the +--- subject where the match occurs. The captured value is a number. +--- +--- Example: +--- ```lua +--- local I = lpeg.Cp() +--- local function anywhere(p) return lpeg.P({I * p * I + 1 * lpeg.V(1)}) end +--- local match_start, match_end = anywhere("world"):match("hello world!") +--- assert(match_start == 7) +--- assert(match_end == 12) +--- ``` +--- +--- @return vim.lpeg.Capture +function vim.lpeg.Cp() end + +--- Creates a substitution capture. This function creates a substitution capture, which +--- captures the substring of the subject that matches `patt`, with substitutions. +--- For any capture inside `patt` with a value, the substring that matched the capture +--- is replaced by the capture value (which should be a string). The final captured +--- value is the string resulting from all replacements. +--- +--- Example: +--- ```lua +--- local function gsub (s, patt, repl) +--- patt = lpeg.P(patt) +--- patt = lpeg.Cs((patt / repl + 1)^0) +--- return lpeg.match(patt, s) +--- end +--- assert(gsub('Hello, xxx!', 'xxx', 'World') == 'Hello, World!') +--- ``` +--- +--- @param patt vim.lpeg.Pattern +--- @return vim.lpeg.Capture +function vim.lpeg.Cs(patt) end + +--- Creates a table capture. This capture returns a table with all values from all anonymous captures +--- made by `patt` inside this table in successive integer keys, starting at 1. +--- Moreover, for each named capture group created by `patt`, the first value of the group is put into +--- the table with the group name as its key. The captured value is only the table. +--- +--- @param patt vim.lpeg.Pattern|'' +--- @return vim.lpeg.Capture +function vim.lpeg.Ct(patt) end + +--- Creates a match-time capture. Unlike all other captures, this one is evaluated immediately when a match occurs +--- (even if it is part of a larger pattern that fails later). It forces the immediate evaluation of all its nested captures +--- and then calls `function`. The given function gets as arguments the entire subject, the current position +--- (after the match of `patt`), plus any capture values produced by `patt`. The first value returned by `function` +--- defines how the match happens. If the call returns a number, the match succeeds and the returned number +--- becomes the new current position. (Assuming a subject sand current position i, the returned number must be +--- in the range [i, len(s) + 1].) If the call returns true, the match succeeds without consuming any input +--- (so, to return true is equivalent to return i). If the call returns false, nil, or no value, the match fails. +--- Any extra values returned by the function become the values produced by the capture. +--- +--- @param patt vim.lpeg.Pattern +--- @param fn function +--- @return vim.lpeg.Capture +function vim.lpeg.Cmt(patt, fn) end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index af676fa961..6ef3cf57e1 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2181,8 +2181,8 @@ vim.bo.ft = vim.bo.filetype --- and the value of that item: --- --- item default Used for ~ ---- stl ' ' or '^' statusline of the current window ---- stlnc ' ' or '=' statusline of the non-current windows +--- stl ' ' statusline of the current window +--- stlnc ' ' statusline of the non-current windows --- wbr ' ' window bar --- horiz '─' or '-' horizontal separators `:split` --- horizup '┴' or '-' upwards facing horizontal separator @@ -2201,9 +2201,7 @@ vim.bo.ft = vim.bo.filetype --- eob '~' empty lines at the end of a buffer --- lastline '@' 'display' contains lastline/truncate --- ---- Any one that is omitted will fall back to the default. For "stl" and ---- "stlnc" the space will be used when there is highlighting, '^' or '=' ---- otherwise. +--- Any one that is omitted will fall back to the default. --- --- Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and --- "verthoriz" are only used when 'laststatus' is 3, since only vertical @@ -3088,9 +3086,8 @@ vim.go.icon = vim.o.icon vim.o.iconstring = "" vim.go.iconstring = vim.o.iconstring ---- Ignore case in search patterns. Also used when searching in the tags ---- file. ---- Also see 'smartcase' and 'tagcase'. +--- Ignore case in search patterns, completion, and when searching the tags file. +--- See also 'smartcase' and 'tagcase'. --- Can be overruled by using "\c" or "\C" in the pattern, see --- `/ignorecase`. --- @@ -3486,12 +3483,11 @@ vim.go.joinspaces = vim.o.joinspaces vim.go.js = vim.go.joinspaces --- List of words that change the behavior of the `jumplist`. ---- stack Make the jumplist behave like the tagstack or like a ---- web browser. Relative location of entries in the ---- jumplist is preserved at the cost of discarding ---- subsequent entries when navigating backwards in the ---- jumplist and then jumping to a location. ---- `jumplist-stack` +--- stack Make the jumplist behave like the tagstack. +--- Relative location of entries in the jumplist is +--- preserved at the cost of discarding subsequent entries +--- when navigating backwards in the jumplist and then +--- jumping to a location. `jumplist-stack` --- --- view When moving through the jumplist, `changelist|, --- |alternate-file` or using `mark-motions` try to @@ -5670,17 +5666,11 @@ vim.bo.sw = vim.bo.shiftwidth --- messages, for example with CTRL-G, and to avoid some other messages. --- It is a list of flags: --- flag meaning when present ~ ---- f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f* ---- i use "[noeol]" instead of "[Incomplete last line]" *shm-i* --- l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* --- m use "[+]" instead of "[Modified]" *shm-m* ---- n use "[New]" instead of "[New File]" *shm-n* --- r use "[RO]" instead of "[readonly]" *shm-r* --- w use "[w]" instead of "written" for file write message *shm-w* --- and "[a]" instead of "appended" for ':w >> file' command ---- x use "[dos]" instead of "[dos format]", "[unix]" *shm-x* ---- instead of "[unix format]" and "[mac]" instead of "[mac ---- format]" --- a all of the above abbreviations *shm-a* --- --- o overwrite message for writing a file with subsequent *shm-o* @@ -5724,7 +5714,7 @@ vim.bo.sw = vim.bo.shiftwidth --- shm=at Abbreviation, and truncate message when necessary. --- --- @type string -vim.o.shortmess = "filnxtToOCF" +vim.o.shortmess = "ltToOCF" vim.o.shm = vim.o.shortmess vim.go.shortmess = vim.o.shortmess vim.go.shm = vim.go.shortmess diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 2062e96b21..6686661a27 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -4528,7 +4528,7 @@ function vim.fn.jobresize(job, width, height) end --- @return any function vim.fn.jobsend(...) end ---- Note: Prefer |vim.system()| in Lua. +--- Note: Prefer |vim.system()| in Lua (unless using the `pty` option). --- --- Spawns {cmd} as a job. --- If {cmd} is a List it runs directly (no 'shell'). @@ -9192,6 +9192,7 @@ function vim.fn.strtrans(string) end --- echo strutf16len('😊') " returns 2 --- echo strutf16len('ą́') " returns 1 --- echo strutf16len('ą́', v:true) " returns 3 +--- < --- --- @param string string --- @param countcc? 0|1 diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 6a3413b597..b83a8dd4b1 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -127,7 +127,7 @@ end --- @param name string local function get_options_info(name) - local info = api.nvim_get_option_info(name) + local info = api.nvim_get_option_info2(name, {}) info.metatype = get_option_metatype(name, info) return info end diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d847c28f5c..5ae4e508ef 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -210,6 +210,7 @@ local extension = { astro = 'astro', atl = 'atlas', as = 'atlas', + zed = 'authzed', ahk = 'autohotkey', au3 = 'autoit', ave = 'ave', @@ -592,6 +593,7 @@ local extension = { ly = 'lilypond', ily = 'lilypond', liquid = 'liquid', + liq = 'liquidsoap', cl = 'lisp', L = 'lisp', lisp = 'lisp', diff --git a/runtime/lua/vim/func.lua b/runtime/lua/vim/func.lua new file mode 100644 index 0000000000..206d1bae95 --- /dev/null +++ b/runtime/lua/vim/func.lua @@ -0,0 +1,41 @@ +local M = {} + +-- TODO(lewis6991): Private for now until: +-- - There are other places in the codebase that could benefit from this +-- (e.g. LSP), but might require other changes to accommodate. +-- - Invalidation of the cache needs to be controllable. Using weak tables +-- is an acceptable invalidation policy, but it shouldn't be the only +-- one. +-- - I don't think the story around `hash` is completely thought out. We +-- may be able to have a good default hash by hashing each argument, +-- so basically a better 'concat'. +-- - Need to support multi level caches. Can be done by allow `hash` to +-- return multiple values. +-- +--- Memoizes a function {fn} using {hash} to hash the arguments. +--- +--- Internally uses a |lua-weaktable| to cache the results of {fn} meaning the +--- cache will be invalidated whenever Lua does garbage collection. +--- +--- The memoized function returns shared references so be wary about +--- mutating return values. +--- +--- @generic F: function +--- @param hash integer|string|function Hash function to create a hash to use as a key to +--- store results. Possible values: +--- - When integer, refers to the index of an argument of {fn} to hash. +--- This argument can have any type. +--- - When function, is evaluated using the same arguments passed to {fn}. +--- - When `concat`, the hash is determined by string concatenating all the +--- arguments passed to {fn}. +--- - When `concat-n`, the hash is determined by string concatenating the +--- first n arguments passed to {fn}. +--- +--- @param fn F Function to memoize. +--- @return F # Memoized version of {fn} +--- @nodoc +function M._memoize(hash, fn) + return require('vim.func._memoize')(hash, fn) +end + +return M diff --git a/runtime/lua/vim/func/_memoize.lua b/runtime/lua/vim/func/_memoize.lua new file mode 100644 index 0000000000..835bf64c93 --- /dev/null +++ b/runtime/lua/vim/func/_memoize.lua @@ -0,0 +1,59 @@ +--- Module for private utility functions + +--- @param argc integer? +--- @return fun(...): any +local function concat_hash(argc) + return function(...) + return table.concat({ ... }, '%%', 1, argc) + end +end + +--- @param idx integer +--- @return fun(...): any +local function idx_hash(idx) + return function(...) + return select(idx, ...) + end +end + +--- @param hash integer|string|fun(...): any +--- @return fun(...): any +local function resolve_hash(hash) + if type(hash) == 'number' then + hash = idx_hash(hash) + elseif type(hash) == 'string' then + local c = hash == 'concat' or hash:match('^concat%-(%d+)') + if c then + hash = concat_hash(tonumber(c)) + else + error('invalid value for hash: ' .. hash) + end + end + --- @cast hash -integer + return hash +end + +--- @generic F: function +--- @param hash integer|string|fun(...): any +--- @param fn F +--- @return F +return function(hash, fn) + vim.validate({ + hash = { hash, { 'number', 'string', 'function' } }, + fn = { fn, 'function' }, + }) + + ---@type table<any,table<any,any>> + local cache = setmetatable({}, { __mode = 'kv' }) + + hash = resolve_hash(hash) + + return function(...) + local key = hash(...) + if cache[key] == nil then + cache[key] = vim.F.pack_len(fn(...)) + end + + return vim.F.unpack_len(cache[key]) + end +end diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index df593be097..bdea95f9ab 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -28,7 +28,7 @@ local keymap = {} --- - "replace_keycodes" defaults to `true` if "expr" is `true`. --- - "noremap": inverse of "remap" (see below). --- - Also accepts: ---- - "buffer": (number|boolean) Creates buffer-local mapping, `0` or `true` +--- - "buffer": (integer|boolean) Creates buffer-local mapping, `0` or `true` --- for current buffer. --- - "remap": (boolean) Make the mapping recursive. Inverse of "noremap". --- Defaults to `false`. @@ -44,7 +44,9 @@ function keymap.set(mode, lhs, rhs, opts) opts = { opts, 't', true }, }) - opts = vim.deepcopy(opts) or {} + opts = vim.deepcopy(opts or {}) + + ---@cast mode string[] mode = type(mode) == 'string' and { mode } or mode if opts.expr and opts.replace_keycodes ~= false then @@ -57,7 +59,7 @@ function keymap.set(mode, lhs, rhs, opts) else -- remaps behavior is opposite of noremap option. opts.noremap = not opts.remap - opts.remap = nil + opts.remap = nil ---@type boolean? end if type(rhs) == 'function' then @@ -66,8 +68,8 @@ function keymap.set(mode, lhs, rhs, opts) end if opts.buffer then - local bufnr = opts.buffer == true and 0 or opts.buffer - opts.buffer = nil + local bufnr = opts.buffer == true and 0 or opts.buffer --[[@as integer]] + opts.buffer = nil ---@type integer? for _, m in ipairs(mode) do vim.api.nvim_buf_set_keymap(bufnr, m, lhs, rhs, opts) end @@ -89,7 +91,7 @@ end --- ``` --- ---@param opts table|nil A table of optional arguments: ---- - "buffer": (number|boolean) Remove a mapping from the given buffer. +--- - "buffer": (integer|boolean) Remove a mapping from the given buffer. --- When `0` or `true`, use the current buffer. ---@see |vim.keymap.set()| --- @@ -103,9 +105,9 @@ function keymap.del(modes, lhs, opts) opts = opts or {} modes = type(modes) == 'string' and { modes } or modes - local buffer = false + local buffer = false ---@type false|integer if opts.buffer ~= nil then - buffer = opts.buffer == true and 0 or opts.buffer + buffer = opts.buffer == true and 0 or opts.buffer --[[@as integer]] end if buffer == false then diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 384d09ee48..9cccaa1d66 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -8,6 +8,7 @@ local M = {} --- to throttle refreshes to at most one at a time local active_refreshes = {} +---@type table<integer, table<integer, lsp.CodeLens[]>> --- bufnr -> client_id -> lenses local lens_cache_by_buf = setmetatable({}, { __index = function(t, b) @@ -16,6 +17,8 @@ local lens_cache_by_buf = setmetatable({}, { end, }) +---@type table<integer, integer> +---client_id -> namespace local namespaces = setmetatable({}, { __index = function(t, key) local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key) @@ -27,6 +30,18 @@ local namespaces = setmetatable({}, { ---@private M.__namespaces = namespaces +local augroup = api.nvim_create_augroup('vim_lsp_codelens', {}) + +api.nvim_create_autocmd('LspDetach', { + group = augroup, + callback = function(ev) + M.clear(ev.data.client_id, ev.buf) + end, +}) + +---@param lens lsp.CodeLens +---@param bufnr integer +---@param client_id integer local function execute_lens(lens, bufnr, client_id) local line = lens.range.start.line api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1) @@ -42,7 +57,7 @@ end --- Return all lenses for the given buffer --- ---@param bufnr integer Buffer number. 0 can be used for the current buffer. ----@return table (`CodeLens[]`) +---@return lsp.CodeLens[] function M.get(bufnr) local lenses_by_client = lens_cache_by_buf[bufnr or 0] if not lenses_by_client then @@ -98,12 +113,17 @@ end ---@param client_id integer|nil filter by client_id. All clients if nil ---@param bufnr integer|nil filter by buffer. All buffers if nil function M.clear(client_id, bufnr) - local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf) + bufnr = bufnr and resolve_bufnr(bufnr) + local buffers = bufnr and { bufnr } + or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs()) for _, iter_bufnr in pairs(buffers) do local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces) for _, iter_client_id in pairs(client_ids) do local ns = namespaces[iter_client_id] - lens_cache_by_buf[iter_bufnr][iter_client_id] = {} + -- there can be display()ed lenses, which are not stored in cache + if lens_cache_by_buf[iter_bufnr] then + lens_cache_by_buf[iter_bufnr][iter_client_id] = {} + end api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1) end end @@ -111,7 +131,7 @@ end --- Display the lenses using virtual text --- ----@param lenses table of lenses to display (`CodeLens[] | null`) +---@param lenses? lsp.CodeLens[] lenses to display ---@param bufnr integer ---@param client_id integer function M.display(lenses, bufnr, client_id) @@ -124,7 +144,8 @@ function M.display(lenses, bufnr, client_id) api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) return end - local lenses_by_lnum = {} + + local lenses_by_lnum = {} ---@type table<integer, lsp.CodeLens[]> for _, lens in pairs(lenses) do local line_lenses = lenses_by_lnum[lens.range.start.line] if not line_lenses then @@ -160,7 +181,7 @@ end --- Store lenses for a specific buffer and client --- ----@param lenses table of lenses to store (`CodeLens[] | null`) +---@param lenses? lsp.CodeLens[] lenses to store ---@param bufnr integer ---@param client_id integer function M.save(lenses, bufnr, client_id) @@ -174,7 +195,7 @@ function M.save(lenses, bufnr, client_id) lens_cache_by_buf[bufnr] = lenses_by_client local ns = namespaces[client_id] api.nvim_buf_attach(bufnr, false, { - on_detach = function(b) + on_detach = function(_, b) lens_cache_by_buf[b] = nil end, on_lines = function(_, b, _, first_lnum, last_lnum) @@ -185,6 +206,10 @@ function M.save(lenses, bufnr, client_id) lenses_by_client[client_id] = lenses end +---@param lenses? lsp.CodeLens[] +---@param bufnr integer +---@param client_id integer +---@param callback fun() local function resolve_lenses(lenses, bufnr, client_id, callback) lenses = lenses or {} local num_lens = vim.tbl_count(lenses) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 4ea3dde81c..d43d9a7cfa 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -371,15 +371,21 @@ function M.hover(_, result, ctx, config) end return end - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then + local format = 'markdown' + local contents ---@type string[] + if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then + format = 'plaintext' + contents = vim.split(result.contents.value or '', '\n', { trimempty = true }) + else + contents = util.convert_input_to_markdown_lines(result.contents) + end + if vim.tbl_isempty(contents) then if config.silent ~= true then vim.notify('No information available') end return end - return util.open_floating_preview(markdown_lines, 'markdown', config) + return util.open_floating_preview(contents, format, config) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover @@ -470,7 +476,6 @@ function M.signature_help(_, result, ctx, config) vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters') local ft = vim.bo[ctx.bufnr].filetype local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers) - lines = util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then if config.silent ~= true then print('No signature help available') diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 023b1c26be..fe06006108 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -32,8 +32,15 @@ function M.check() vim.health.start('vim.lsp: Active Clients') if next(clients) then for _, client in pairs(clients) do + local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',') report_info( - string.format('%s (id=%s, root_dir=%s)', client.name, client.id, client.config.root_dir) + string.format( + '%s (id=%s, root_dir=%s, attached_to=[%s])', + client.name, + client.id, + vim.fn.fnamemodify(client.config.root_dir, ':~'), + attached_to + ) ) end else diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 54721865b7..0d1e3cc0d1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -102,7 +102,7 @@ end local function split_lines(value) value = string.gsub(value, '\r\n?', '\n') - return split(value, '\n', { plain = true }) + return split(value, '\n', { plain = true, trimempty = true }) end local function create_window_without_focus() @@ -877,9 +877,12 @@ end --- window for `textDocument/hover`, for parsing the result of --- `textDocument/signatureHelp`, and potentially others. --- +--- Note that if the input is of type `MarkupContent` and its kind is `plaintext`, +--- then the corresponding value is returned without further modifications. +--- ---@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`) ---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}. ----@return table {contents} extended with lines of converted markdown. +---@return string[] extended with lines of converted markdown. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} @@ -887,27 +890,13 @@ function M.convert_input_to_markdown_lines(input, contents) if type(input) == 'string' then list_extend(contents, split_lines(input)) else - assert(type(input) == 'table', 'Expected a table for Hover.contents') + assert(type(input) == 'table', 'Expected a table for LSP input') -- MarkupContent if input.kind then - -- The kind can be either plaintext or markdown. - -- If it's plaintext, then wrap it in a <text></text> block - - -- Some servers send input.value as empty, so let's ignore this :( local value = input.value or '' - - if input.kind == 'plaintext' then - -- wrap this in a <text></text> block so that stylize_markdown - -- can properly process it as plaintext - value = string.format('<text>\n%s\n</text>', value) - end - - -- assert(type(value) == 'string') list_extend(contents, split_lines(value)) -- MarkupString variation 2 elseif input.language then - -- Some servers send input.value as empty, so let's ignore this :( - -- assert(type(input.value) == 'string') table.insert(contents, '```' .. input.language) list_extend(contents, split_lines(input.value or '')) table.insert(contents, '```') @@ -925,7 +914,7 @@ function M.convert_input_to_markdown_lines(input, contents) return contents end ---- Converts `textDocument/SignatureHelp` response to markdown lines. +--- Converts `textDocument/signatureHelp` response to markdown lines. --- ---@param signature_help table Response of `textDocument/SignatureHelp` ---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block @@ -955,10 +944,10 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers end local label = signature.label if ft then - -- wrap inside a code block so stylize_markdown can render it properly + -- wrap inside a code block for proper rendering label = ('```%s\n%s\n```'):format(ft, label) end - list_extend(contents, split(label, '\n', { plain = true })) + list_extend(contents, split(label, '\n', { plain = true, trimempty = true })) if signature.documentation then -- if LSP returns plain string, we treat it as plaintext. This avoids -- special characters like underscore or similar from being interpreted @@ -1223,7 +1212,7 @@ function M.preview_location(location, opts) local syntax = vim.bo[bufnr].syntax if syntax == '' then -- When no syntax is set, we use filetype as fallback. This might not result - -- in a valid syntax definition. See also ft detection in stylize_markdown. + -- in a valid syntax definition. -- An empty syntax is more common now with TreeSitter, since TS disables syntax. syntax = vim.bo[bufnr].filetype end @@ -1240,36 +1229,65 @@ local function find_window_by_var(name, value) end end ---- Trims empty lines from input and pad top and bottom with empty lines ---- ----@param contents table of lines to trim and pad ----@param opts table with optional fields ---- - pad_top number of lines to pad contents at top (default 0) ---- - pad_bottom number of lines to pad contents at bottom (default 0) ----@return table table of trimmed and padded lines -function M._trim(contents, opts) - validate({ - contents = { contents, 't' }, - opts = { opts, 't', true }, - }) - opts = opts or {} - contents = M.trim_empty_lines(contents) - if opts.pad_top then - for _ = 1, opts.pad_top do - table.insert(contents, 1, '') +---Returns true if the line is empty or only contains whitespace. +---@param line string +---@return boolean +local function is_blank_line(line) + return line and line:match('^%s*$') +end + +---Returns true if the line corresponds to a Markdown thematic break. +---@param line string +---@return boolean +local function is_separator_line(line) + return line and line:match('^ ? ? ?%-%-%-+%s*$') +end + +---Replaces separator lines by the given divider and removing surrounding blank lines. +---@param contents string[] +---@param divider string +---@return string[] +local function replace_separators(contents, divider) + local trimmed = {} + local l = 1 + while l <= #contents do + local line = contents[l] + if is_separator_line(line) then + if l > 1 and is_blank_line(contents[l - 1]) then + table.remove(trimmed) + end + table.insert(trimmed, divider) + if is_blank_line(contents[l + 1]) then + l = l + 1 + end + else + table.insert(trimmed, line) end + l = l + 1 end - if opts.pad_bottom then - for _ = 1, opts.pad_bottom do - table.insert(contents, '') + + return trimmed +end + +---Collapses successive blank lines in the input table into a single one. +---@param contents string[] +---@return string[] +local function collapse_blank_lines(contents) + local collapsed = {} + local l = 1 + while l <= #contents do + local line = contents[l] + if is_blank_line(line) then + while is_blank_line(contents[l + 1]) do + l = l + 1 + end end + table.insert(collapsed, line) + l = l + 1 end - return contents + return collapsed end ---- Generates a table mapping markdown code block lang to vim syntax, ---- based on g:markdown_fenced_languages ----@return table table of lang -> syntax mappings local function get_markdown_fences() local fences = {} for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do @@ -1297,8 +1315,6 @@ end --- - wrap_at character to wrap at for computing height --- - max_width maximal width of floating window --- - max_height maximal height of floating window ---- - pad_top number of lines to pad contents at top ---- - pad_bottom number of lines to pad contents at bottom --- - separator insert separator after code block ---@return table stripped content function M.stylize_markdown(bufnr, contents, opts) @@ -1335,7 +1351,7 @@ function M.stylize_markdown(bufnr, contents, opts) end -- Clean up - contents = M._trim(contents, opts) + contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true }) local stripped = {} local highlights = {} @@ -1484,6 +1500,45 @@ function M.stylize_markdown(bufnr, contents, opts) return stripped end +--- @class lsp.util.NormalizeMarkdownOptions +--- @field width integer Thematic breaks are expanded to this size. Defaults to 80. + +--- Normalizes Markdown input to a canonical form. +--- +--- The returned Markdown adheres to the GitHub Flavored Markdown (GFM) +--- specification. +--- +--- The following transformations are made: +--- +--- 1. Carriage returns ('\r') and empty lines at the beginning and end are removed +--- 2. Successive empty lines are collapsed into a single empty line +--- 3. Thematic breaks are expanded to the given width +--- +---@private +---@param contents string[] +---@param opts? lsp.util.NormalizeMarkdownOptions +---@return string[] table of lines containing normalized Markdown +---@see https://github.github.com/gfm +function M._normalize_markdown(contents, opts) + validate({ + contents = { contents, 't' }, + opts = { opts, 't', true }, + }) + opts = opts or {} + + -- 1. Carriage returns are removed + contents = vim.split(table.concat(contents, '\n'):gsub('\r', ''), '\n', { trimempty = true }) + + -- 2. Successive empty lines are collapsed into a single empty line + contents = collapse_blank_lines(contents) + + -- 3. Thematic breaks are expanded to the given width + local divider = string.rep('─', opts.width or 80) + contents = replace_separators(contents, divider) + + return contents +end + --- Closes the preview window --- ---@param winnr integer window id of preview window @@ -1620,8 +1675,6 @@ end --- - wrap_at: (integer) character to wrap at for computing height when wrap is enabled --- - max_width: (integer) maximal width of floating window --- - max_height: (integer) maximal height of floating window ---- - pad_top: (integer) number of lines to pad contents at top ---- - pad_bottom: (integer) number of lines to pad contents at bottom --- - focus_id: (string) if a popup with this id is opened, then focus it --- - close_events: (table) list of events that closes the floating window --- - focusable: (boolean, default true) Make float focusable @@ -1629,8 +1682,7 @@ end --- is also `true`, focus an existing floating window with the same --- {focus_id} ---@return integer bufnr of newly created float window ----@return integer winid of newly created float window ----preview window +---@return integer winid of newly created float window preview window function M.open_floating_preview(contents, syntax, opts) validate({ contents = { contents, 't' }, @@ -1639,7 +1691,6 @@ function M.open_floating_preview(contents, syntax, opts) }) opts = opts or {} opts.wrap = opts.wrap ~= false -- wrapping by default - opts.stylize_markdown = opts.stylize_markdown ~= false and vim.g.syntax_on ~= nil opts.focus = opts.focus ~= false opts.close_events = opts.close_events or { 'CursorMoved', 'CursorMovedI', 'InsertCharPre' } @@ -1671,16 +1722,21 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_win_close(existing_float, true) end + -- Create the buffer local floating_bufnr = api.nvim_create_buf(false, true) - local do_stylize = syntax == 'markdown' and opts.stylize_markdown - - -- Clean up input: trim empty lines from the end, pad - contents = M._trim(contents, opts) + -- Set up the contents, using treesitter for markdown + local do_stylize = syntax == 'markdown' and vim.g.syntax_on ~= nil if do_stylize then - -- applies the syntax and sets the lines to the buffer - contents = M.stylize_markdown(floating_bufnr, contents, opts) + local width = M._make_floating_popup_size(contents, opts) + contents = M._normalize_markdown(contents, { width = width }) + vim.bo[floating_bufnr].filetype = 'markdown' + vim.treesitter.start(floating_bufnr) + api.nvim_buf_set_lines(floating_bufnr, 0, -1, false, contents) else + -- Clean up input: trim empty lines + contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true }) + if syntax then vim.bo[floating_bufnr].syntax = syntax end @@ -1697,9 +1753,9 @@ function M.open_floating_preview(contents, syntax, opts) local float_option = M.make_floating_popup_options(width, height, opts) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) + if do_stylize then vim.wo[floating_winnr].conceallevel = 2 - vim.wo[floating_winnr].concealcursor = 'n' end -- disable folding vim.wo[floating_winnr].foldenable = false @@ -1708,6 +1764,7 @@ function M.open_floating_preview(contents, syntax, opts) vim.bo[floating_bufnr].modifiable = false vim.bo[floating_bufnr].bufhidden = 'wipe' + api.nvim_buf_set_keymap( floating_bufnr, 'n', @@ -1908,6 +1965,7 @@ function M.symbols_to_items(symbols, bufnr) end --- Removes empty lines from the beginning and end. +---@deprecated use `vim.split()` with `trimempty` instead ---@param lines table list of lines to trim ---@return table trimmed list of lines function M.trim_empty_lines(lines) @@ -2172,6 +2230,35 @@ function M.lookup_section(settings, section) return settings end +--- Converts line range (0-based, end-inclusive) to lsp range, +--- handles absence of a trailing newline +--- +---@param bufnr integer +---@param start_line integer +---@param end_line integer +---@param offset_encoding lsp.PositionEncodingKind +---@return lsp.Range +local function make_line_range_params(bufnr, start_line, end_line, offset_encoding) + local last_line = api.nvim_buf_line_count(bufnr) - 1 + + ---@type lsp.Position + local end_pos + + if end_line == last_line and not vim.api.nvim_get_option_value('endofline', { buf = bufnr }) then + end_pos = { + line = end_line, + character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding), + } + else + end_pos = { line = end_line + 1, character = 0 } + end + + return { + start = { line = start_line, character = 0 }, + ['end'] = end_pos, + } +end + ---@private --- Request updated LSP information for a buffer. --- @@ -2195,6 +2282,8 @@ function M._refresh(method, opts) return end + local textDocument = M.make_text_document_params(bufnr) + local only_visible = opts.only_visible or false if only_visible then @@ -2202,28 +2291,25 @@ function M._refresh(method, opts) if api.nvim_win_get_buf(window) == bufnr then local first = vim.fn.line('w0', window) local last = vim.fn.line('w$', window) - local params = { - textDocument = M.make_text_document_params(bufnr), - range = { - start = { line = first - 1, character = 0 }, - ['end'] = { line = last, character = 0 }, - }, - } for _, client in ipairs(clients) do - client.request(method, params, nil, bufnr) + client.request(method, { + textDocument = textDocument, + range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding), + }, nil, bufnr) end end end else - local params = { - textDocument = M.make_text_document_params(bufnr), - range = { - start = { line = 0, character = 0 }, - ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, - }, - } for _, client in ipairs(clients) do - client.request(method, params, nil, bufnr) + client.request(method, { + textDocument = textDocument, + range = make_line_range_params( + bufnr, + 0, + api.nvim_buf_line_count(bufnr) - 1, + client.offset_encoding + ), + }, nil, bufnr) end end end diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 893b3e1877..d29c356af3 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -2,9 +2,9 @@ local M = {} --- Reads trust database from $XDG_STATE_HOME/nvim/trust. --- ----@return (table) Contents of trust database, if it exists. Empty table otherwise. +---@return table<string, string> Contents of trust database, if it exists. Empty table otherwise. local function read_trust() - local trust = {} + local trust = {} ---@type table<string, string> local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r') if f then local contents = f:read('*a') @@ -24,12 +24,12 @@ end --- Writes provided {trust} table to trust database at --- $XDG_STATE_HOME/nvim/trust. --- ----@param trust (table) Trust table to write +---@param trust table<string, string> Trust table to write local function write_trust(trust) vim.validate({ trust = { trust, 't' } }) local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w')) - local t = {} + local t = {} ---@type string[] for p, h in pairs(trust) do t[#t + 1] = string.format('%s %s\n', h, p) end @@ -61,7 +61,7 @@ function M.read(path) return nil end - local contents + local contents ---@type string? do local f = io.open(fullpath, 'r') if not f then @@ -108,6 +108,11 @@ function M.read(path) return contents end +---@class vim.trust.opts +---@field action string +---@field path? string +---@field bufnr? integer + --- Manage the trust database. --- --- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. @@ -134,6 +139,7 @@ function M.trust(opts) }, }) + ---@cast opts vim.trust.opts local path = opts.path local bufnr = opts.bufnr local action = opts.action @@ -144,7 +150,7 @@ function M.trust(opts) assert(not path, '"path" is not valid when action is "allow"') end - local fullpath + local fullpath ---@type string? if path then fullpath = vim.uv.fs_realpath(vim.fs.normalize(path)) elseif bufnr then @@ -165,7 +171,8 @@ function M.trust(opts) if action == 'allow' then local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n' - local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline) + local contents = + table.concat(vim.api.nvim_buf_get_lines(bufnr --[[@as integer]], 0, -1, false), newline) if vim.bo[bufnr].endofline then contents = contents .. newline end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 0c38fa955a..9542d93789 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -534,12 +534,12 @@ function vim.tbl_flatten(t) return result end ---- Enumerate a table sorted by its keys. +--- Enumerates key-value pairs of a table, ordered by key. --- ---@see Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ---@param t table Dict-like table ----@return function iterator over sorted keys and their values +---@return function # |for-in| iterator over sorted keys and their values function vim.spairs(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -551,7 +551,6 @@ function vim.spairs(t) table.sort(keys) -- Return the iterator function. - -- TODO(justinmk): Return "iterator function, table {t}, and nil", like pairs()? local i = 0 return function() i = i + 1 @@ -561,11 +560,14 @@ function vim.spairs(t) end end ---- Tests if a Lua table can be treated as an array (a table indexed by integers). +--- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous). --- ---- Empty table `{}` is assumed to be an array, unless it was created by ---- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, ---- for example from |rpcrequest()| or |vim.fn|. +--- If the indexes start from 1 and are contiguous then the array is also a list. |vim.tbl_islist()| +--- +--- Empty table `{}` is an array, unless it was created by |vim.empty_dict()| or returned as +--- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|. +--- +---@see https://github.com/openresty/luajit2#tableisarray --- ---@param t table ---@return boolean `true` if array-like table, else `false`. @@ -597,11 +599,13 @@ function vim.tbl_isarray(t) end end ---- Tests if a Lua table can be treated as a list (a table indexed by consecutive integers starting from 1). +--- Tests if `t` is a "list": a table indexed _only_ by contiguous integers starting from 1 (what +--- |lua-length| calls a "regular array"). +--- +--- Empty table `{}` is a list, unless it was created by |vim.empty_dict()| or returned as +--- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|. --- ---- Empty table `{}` is assumed to be an list, unless it was created by ---- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result, ---- for example from |rpcrequest()| or |vim.fn|. +---@see |vim.tbl_isarray()| --- ---@param t table ---@return boolean `true` if list-like table, else `false`. @@ -705,61 +709,6 @@ function vim.endswith(s, suffix) return #suffix == 0 or s:sub(-#suffix) == suffix end ---- Validates a parameter specification (types and values). ---- ---- Usage example: ---- ---- ```lua ---- function user.new(name, age, hobbies) ---- vim.validate{ ---- name={name, 'string'}, ---- age={age, 'number'}, ---- hobbies={hobbies, 'table'}, ---- } ---- ... ---- end ---- ``` ---- ---- Examples with explicit argument values (can be run directly): ---- ---- ```lua ---- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} ---- --> NOP (success) ---- ---- vim.validate{arg1={1, 'table'}} ---- --> error('arg1: expected table, got number') ---- ---- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} ---- --> error('arg1: expected even number, got 3') ---- ``` ---- ---- If multiple types are valid they can be given as a list. ---- ---- ```lua ---- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} ---- -- NOP (success) ---- ---- vim.validate{arg1={1, {'string', 'table'}}} ---- -- error('arg1: expected string|table, got number') ---- ---- ``` ---- ----@param opt table Names of parameters to validate. Each key is a parameter ---- name; each value is a tuple in one of these forms: ---- 1. (arg_value, type_name, optional) ---- - arg_value: argument value ---- - type_name: string|table type name, one of: ("table", "t", "string", ---- "s", "number", "n", "boolean", "b", "function", "f", "nil", ---- "thread", "userdata") or list of them. ---- - optional: (optional) boolean, if true, `nil` is valid ---- 2. (arg_value, fn, msg) ---- - arg_value: argument value ---- - fn: any function accepting one argument, returns true if and ---- only if the argument is valid. Can optionally return an additional ---- informative error message as the second returned value. ---- - msg: (optional) error string if validation fails -function vim.validate(opt) end -- luacheck: no unused - do local type_names = { ['table'] = 'table', @@ -844,6 +793,59 @@ do return true, nil end + --- Validates a parameter specification (types and values). + --- + --- Usage example: + --- + --- ```lua + --- function user.new(name, age, hobbies) + --- vim.validate{ + --- name={name, 'string'}, + --- age={age, 'number'}, + --- hobbies={hobbies, 'table'}, + --- } + --- ... + --- end + --- ``` + --- + --- Examples with explicit argument values (can be run directly): + --- + --- ```lua + --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} + --- --> NOP (success) + --- + --- vim.validate{arg1={1, 'table'}} + --- --> error('arg1: expected table, got number') + --- + --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + --- --> error('arg1: expected even number, got 3') + --- ``` + --- + --- If multiple types are valid they can be given as a list. + --- + --- ```lua + --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} + --- -- NOP (success) + --- + --- vim.validate{arg1={1, {'string', 'table'}}} + --- -- error('arg1: expected string|table, got number') + --- + --- ``` + --- + ---@param opt table Names of parameters to validate. Each key is a parameter + --- name; each value is a tuple in one of these forms: + --- 1. (arg_value, type_name, optional) + --- - arg_value: argument value + --- - type_name: string|table type name, one of: ("table", "t", "string", + --- "s", "number", "n", "boolean", "b", "function", "f", "nil", + --- "thread", "userdata") or list of them. + --- - optional: (optional) boolean, if true, `nil` is valid + --- 2. (arg_value, fn, msg) + --- - arg_value: argument value + --- - fn: any function accepting one argument, returns true if and + --- only if the argument is valid. Can optionally return an additional + --- informative error message as the second returned value. + --- - msg: (optional) error string if validation fails function vim.validate(opt) local ok, err_msg = is_valid(opt) if not ok then @@ -866,28 +868,25 @@ function vim.is_callable(f) return type(m.__call) == 'function' end ---- Creates a table whose members are automatically created when accessed, if they don't already ---- exist. ---- ---- They mimic defaultdict in python. +--- Creates a table whose missing keys are provided by {createfn} (like Python's "defaultdict"). --- ---- If {create} is `nil`, this will create a defaulttable whose constructor function is ---- this function, effectively allowing to create nested tables on the fly: +--- If {createfn} is `nil` it defaults to defaulttable() itself, so accessing nested keys creates +--- nested tables: --- --- ```lua --- local a = vim.defaulttable() --- a.b.c = 1 --- ``` --- ----@param create function?(key:any):any The function called to create a missing value. ----@return table Empty table with metamethod -function vim.defaulttable(create) - create = create or function(_) +---@param createfn function?(key:any):any Provides the value for a missing `key`. +---@return table # Empty table with `__index` metamethod. +function vim.defaulttable(createfn) + createfn = createfn or function(_) return vim.defaulttable() end return setmetatable({}, { __index = function(tbl, key) - rawset(tbl, key, create(key)) + rawset(tbl, key, createfn(key)) return rawget(tbl, key) end, }) diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index cc8be72670..0e34cbcbcc 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -127,6 +127,7 @@ function M.get_parser(bufnr, lang, opts) ) end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then + assert(lang, 'lang should be valid') parsers[bufnr] = M._create_parser(bufnr, lang, opts) end @@ -162,7 +163,7 @@ function M.is_ancestor(dest, source) return false end - local current = source + local current = source ---@type TSNode? while current ~= nil do if current == dest then return true @@ -491,7 +492,7 @@ end --- function, it accepts the buffer number of the source buffer as its only --- argument and should return a string. function M.inspect_tree(opts) - ---@cast opts InspectTreeOpts + ---@diagnostic disable-next-line: invisible require('vim.treesitter.dev').inspect_tree(opts) end diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 9a94f12c16..d01b7be3b0 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -50,7 +50,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) ---@class TSParser ----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: true): TSTree, Range6[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: false|nil): TSTree, Range4[] ---@field reset fun(self: TSParser) ---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index abf0bf345d..87d74789a3 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -10,20 +10,12 @@ local M = {} --- @alias vim.treesitter.ParseError {msg: string, range: Range4} ---- @private ---- Caches parse results for queries for each language. ---- Entries of parse_cache[lang][query_text] will either be true for successful parse or contain the ---- message and range of the parse error. ---- @type table<string,table<string,vim.treesitter.ParseError|true>> -local parse_cache = {} - --- Contains language dependent context for the query linter --- @class QueryLinterLanguageContext --- @field lang string? Current `lang` of the targeted parser --- @field parser_info table? Parser info returned by vim.treesitter.language.inspect --- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs` ---- @private --- Adds a diagnostic for node in the query buffer --- @param diagnostics Diagnostic[] --- @param range Range4 @@ -42,7 +34,6 @@ local function add_lint_for_node(diagnostics, range, lint, lang) } end ---- @private --- Determines the target language of a query file by its path: <lang>/<query_type>.scm --- @param buf integer --- @return string? @@ -53,7 +44,6 @@ local function guess_query_lang(buf) end end ---- @private --- @param buf integer --- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil --- @return QueryLinterNormalizedOpts @@ -87,7 +77,6 @@ local lint_query = [[;; query (ERROR) @error ]] ---- @private --- @param err string --- @param node TSNode --- @return vim.treesitter.ParseError @@ -112,38 +101,26 @@ local function get_error_entry(err, node) } end ---- @private --- @param node TSNode --- @param buf integer --- @param lang string ---- @param diagnostics Diagnostic[] -local function check_toplevel(node, buf, lang, diagnostics) - local query_text = vim.treesitter.get_node_text(node, buf) - - if not parse_cache[lang] then - parse_cache[lang] = {} - end - - local lang_cache = parse_cache[lang] - - if lang_cache[query_text] == nil then - local cache_val, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|Query - - if not cache_val and type(err) == 'string' then - cache_val = get_error_entry(err, node) - end - - lang_cache[query_text] = cache_val - end +local function hash_parse(node, buf, lang) + return tostring(node:id()) .. tostring(buf) .. tostring(vim.b[buf].changedtick) .. lang +end - local cache_entry = lang_cache[query_text] +--- @param node TSNode +--- @param buf integer +--- @param lang string +--- @return vim.treesitter.ParseError? +local parse = vim.func._memoize(hash_parse, function(node, buf, lang) + local query_text = vim.treesitter.get_node_text(node, buf) + local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|Query - if type(cache_entry) ~= 'boolean' then - add_lint_for_node(diagnostics, cache_entry.range, cache_entry.msg, lang) + if not ok and type(err) == 'string' then + return get_error_entry(err, node) end -end +end) ---- @private --- @param buf integer --- @param match table<integer,TSNode> --- @param query Query @@ -164,7 +141,10 @@ local function lint_match(buf, match, query, lang_context, diagnostics) -- other checks rely on Neovim parser introspection if lang and parser_info and cap_id == 'toplevel' then - check_toplevel(node, buf, lang, diagnostics) + local err = parse(node, buf, lang) + if err then + add_lint_for_node(diagnostics, err.range, err.msg, lang) + end end end end diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 7f24ba8590..db30d638af 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -258,7 +258,7 @@ end --- @private --- ---- @param opts InspectTreeOpts +--- @param opts InspectTreeOpts? function M.inspect_tree(opts) vim.validate({ opts = { opts, 't', true }, @@ -597,8 +597,9 @@ function M.edit_query(lang) }) api.nvim_buf_set_lines(query_buf, 0, -1, false, { - ';; Write your query here. Use @captures to highlight matches in the source buffer.', - ';; Completion for grammar nodes is available (see :h compl-omni)', + ';; Write queries here (see $VIMRUNTIME/queries/ for examples).', + ';; Move cursor to a capture ("@foo") to highlight matches in the source buffer.', + ';; Completion for grammar nodes is available (:help compl-omni)', '', '', }) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b555ee231b..670f2797b7 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -78,13 +78,14 @@ local TSCallbackNames = { ---@field private _opts table Options ---@field private _parser TSParser Parser for language ---@field private _has_regions boolean ----@field private _regions Range6[][]? +---@field private _regions table<integer, Range6[]>? ---List of regions this tree should manage and parse. If nil then regions are ---taken from _trees. This is mostly a short-lived cache for included_regions() ---@field private _lang string Language name ---@field private _parent_lang? string Parent language name ---@field private _source (integer|string) Buffer or string to parse ----@field private _trees TSTree[] Reference to parsed tree (one for each language) +---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language). +---Each key is the index of region, which is synced with _regions and _valid. ---@field private _valid boolean|table<integer,boolean> If the parsed tree is valid ---@field private _logger? fun(logtype: string, msg: string) ---@field private _logfile? file* @@ -211,7 +212,7 @@ function LanguageTree:_log(...) end local info = debug.getinfo(2, 'nl') - local nregions = #self:included_regions() + local nregions = vim.tbl_count(self:included_regions()) local prefix = string.format('%s:%d: (#regions=%d) ', info.name or '???', info.currentline or 0, nregions) @@ -244,8 +245,13 @@ function LanguageTree:invalidate(reload) end end ---- Returns all trees this language tree contains. +--- Returns all trees of the regions parsed by this parser. --- Does not include child languages. +--- The result is list-like if +--- * this LanguageTree is the root, in which case the result is empty or a singleton list; or +--- * the root LanguageTree is fully parsed. +--- +---@return table<integer, TSTree> function LanguageTree:trees() return self._trees end @@ -255,16 +261,15 @@ function LanguageTree:lang() return self._lang end ---- Determines whether this tree is valid. ---- If the tree is invalid, call `parse()`. ---- This will return the updated tree. ----@param exclude_children boolean|nil +--- Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest +--- state of the source. If invalid, user should call |LanguageTree:parse()|. +---@param exclude_children boolean|nil whether to ignore the validity of children (default `false`) ---@return boolean function LanguageTree:is_valid(exclude_children) local valid = self._valid if type(valid) == 'table' then - for i = 1, #self:included_regions() do + for i, _ in pairs(self:included_regions()) do if not valid[i] then return false end @@ -328,7 +333,7 @@ end --- @private --- @param range boolean|Range? ---- @return integer[] changes +--- @return Range6[] changes --- @return integer no_regions_parsed --- @return number total_parse_time function LanguageTree:_parse_regions(range) @@ -370,7 +375,7 @@ function LanguageTree:_add_injections() local seen_langs = {} ---@type table<string,boolean> local query_time, injections_by_lang = tcall(self._get_injections, self) - for lang, injection_ranges in pairs(injections_by_lang) do + for lang, injection_regions in pairs(injections_by_lang) do local has_lang = pcall(language.add, lang) -- Child language trees should just be ignored if not found, since @@ -383,7 +388,7 @@ function LanguageTree:_add_injections() child = self:add_child(lang) end - child:set_included_regions(injection_ranges) + child:set_included_regions(injection_regions) seen_langs[lang] = true end end @@ -408,14 +413,14 @@ end --- Set to `true` to run a complete parse of the source (Note: Can be slow!) --- Set to `false|nil` to only parse regions with empty ranges (typically --- only the root tree without injections). ---- @return TSTree[] +--- @return table<integer, TSTree> function LanguageTree:parse(range) if self:is_valid() then self:_log('valid') return self._trees end - local changes --- @type Range6? + local changes --- @type Range6[]? -- Collect some stats local no_regions_parsed = 0 @@ -565,7 +570,7 @@ function LanguageTree:_iter_regions(fn) local all_valid = true - for i, region in ipairs(self:included_regions()) do + for i, region in pairs(self:included_regions()) do if was_valid or self._valid[i] then self._valid[i] = fn(i, region) if not self._valid[i] then @@ -601,7 +606,7 @@ end --- nodes, which is useful for templating languages like ERB and EJS. --- ---@private ----@param new_regions Range6[][] List of regions this tree should manage and parse. +---@param new_regions (Range4|Range6|TSNode)[][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(new_regions) self._has_regions = true @@ -609,16 +614,20 @@ function LanguageTree:set_included_regions(new_regions) for _, region in ipairs(new_regions) do for i, range in ipairs(region) do if type(range) == 'table' and #range == 4 then - region[i] = Range.add_bytes(self._source, range) + region[i] = Range.add_bytes(self._source, range --[[@as Range4]]) elseif type(range) == 'userdata' then region[i] = { range:range(true) } end end end + -- included_regions is not guaranteed to be list-like, but this is still sound, i.e. if + -- new_regions is different from included_regions, then outdated regions in included_regions are + -- invalidated. For example, if included_regions = new_regions ++ hole ++ outdated_regions, then + -- outdated_regions is invalidated by _iter_regions in else branch. if #self:included_regions() ~= #new_regions then -- TODO(lewis6991): inefficient; invalidate trees incrementally - for _, t in ipairs(self._trees) do + for _, t in pairs(self._trees) do self:_do_callback('changedtree', t:included_ranges(true), t) end self._trees = {} @@ -632,20 +641,22 @@ function LanguageTree:set_included_regions(new_regions) self._regions = new_regions end ----Gets the set of included regions ----@return Range6[][] +---Gets the set of included regions managed by this LanguageTree. This can be different from the +---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions +---outside the requested range. +---@return table<integer, Range6[]> function LanguageTree:included_regions() if self._regions then return self._regions end - if not self._has_regions or #self._trees == 0 then - -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} + if not self._has_regions then + -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} (the full range) return { {} } end local regions = {} ---@type Range6[][] - for i, _ in ipairs(self._trees) do + for i, _ in pairs(self._trees) do regions[i] = self._trees[i]:included_ranges(true) end @@ -727,12 +738,14 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges) end -- TODO(clason): replace by refactored `ts.has_parser` API (without registering) ----@param lang string parser name ----@return boolean # true if parser for {lang} exists on rtp -local has_parser = function(lang) +--- The result of this function is cached to prevent nvim_get_runtime_file from being +--- called too often +--- @param lang string parser name +--- @return boolean # true if parser for {lang} exists on rtp +local has_parser = vim.func._memoize(1, function(lang) return vim._ts_has_language(lang) or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 -end +end) --- Return parser name for language (if exists) or filetype (if registered and exists). --- Also attempts with the input lower-cased. @@ -801,7 +814,7 @@ local function combine_regions(regions) return result end ---- Gets language injection points by language. +--- Gets language injection regions by language. --- --- This is where most of the injection processing occurs. --- diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 44ed37d64e..8cbbffcd60 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -191,12 +191,6 @@ function M.set(lang, query_name, text) explicit_queries[lang][query_name] = M.parse(lang, text) end ---- `false` if query files didn't exist or were empty ----@type table<string, table<string, Query|false>> -local query_get_cache = vim.defaulttable(function() - return setmetatable({}, { __mode = 'v' }) -end) - ---@deprecated function M.get_query(...) vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') @@ -209,34 +203,19 @@ end ---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query|nil Parsed query -function M.get(lang, query_name) +M.get = vim.func._memoize('concat-2', function(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] end - local cached = query_get_cache[lang][query_name] - if cached then - return cached - elseif cached == false then - return nil - end - local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) if #query_string == 0 then - query_get_cache[lang][query_name] = false return nil end - local query = M.parse(lang, query_string) - query_get_cache[lang][query_name] = query - return query -end - ----@type table<string, table<string, Query>> -local query_parse_cache = vim.defaulttable(function() - return setmetatable({}, { __mode = 'v' }) + return M.parse(lang, query_string) end) ---@deprecated @@ -262,20 +241,15 @@ end ---@param query string Query in s-expr syntax --- ---@return Query Parsed query -function M.parse(lang, query) +M.parse = vim.func._memoize('concat-2', function(lang, query) language.add(lang) - local cached = query_parse_cache[lang][query] - if cached then - return cached - end local self = setmetatable({}, Query) self.query = vim._ts_parse_query(lang, query) self.info = self.query:inspect() self.captures = self.info.captures - query_parse_cache[lang][query] = self return self -end +end) ---@deprecated function M.get_range(...) @@ -843,11 +817,13 @@ function M.omnifunc(findstart, base) return require('vim.treesitter._query_linter').omnifunc(findstart, base) end ---- Open a window for live editing of a treesitter query. +--- Opens a live editor to query the buffer you started from. --- ---- Can also be shown with `:EditQuery`. *:EditQuery* +--- Can also be shown with *:EditQuery*. --- ---- Note that the editor opens a scratch buffer, and so queries aren't persisted on disk. +--- If you move the cursor to a capture name ("@foo"), text matching the capture is highlighted in +--- the source buffer. The query editor is a scratch buffer, use `:write` to save it. You can find +--- example queries at `$VIMRUNTIME/queries/`. --- --- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype. function M.edit(lang) diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 9dce80b77e..2dc817c5c1 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -60,7 +60,7 @@ end ---@param path string Path to file ---@return string URI function M.uri_from_fname(path) - local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') + local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string? local is_windows = volume_path ~= nil if is_windows then path = volume_path .. M.uri_encode(fname:gsub('\\', '/')) @@ -82,7 +82,7 @@ function M.uri_from_bufnr(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) local volume_path = fname:match('^([a-zA-Z]:).*') local is_windows = volume_path ~= nil - local scheme + local scheme ---@type string? if is_windows then fname = fname:gsub('\\', '/') scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN) @@ -107,10 +107,9 @@ function M.uri_to_fname(uri) uri = M.uri_decode(uri) --TODO improve this. if is_windows_file_uri(uri) then - uri = uri:gsub('^file:/+', '') - uri = uri:gsub('/', '\\') + uri = uri:gsub('^file:/+', ''):gsub('/', '\\') else - uri = uri:gsub('^file:/+', '/') + uri = uri:gsub('^file:/+', '/') ---@type string end return uri end diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm index e78d233cc6..2cc5546bac 100644 --- a/runtime/queries/markdown/highlights.scm +++ b/runtime/queries/markdown/highlights.scm @@ -61,3 +61,11 @@ ] @string.escape (inline) @spell + +;; Conceal backticks +(fenced_code_block + (fenced_code_block_delimiter) @conceal + (#set! conceal "")) +(fenced_code_block + (info_string (language) @conceal + (#set! conceal ""))) diff --git a/runtime/queries/markdown_inline/highlights.scm b/runtime/queries/markdown_inline/highlights.scm index cd5da530d7..c75da478af 100644 --- a/runtime/queries/markdown_inline/highlights.scm +++ b/runtime/queries/markdown_inline/highlights.scm @@ -92,3 +92,11 @@ "]" ] @conceal (#set! conceal "")) + +;; Replace common HTML entities. +((entity_reference) @conceal (#eq? @conceal " ") (#set! conceal "")) +((entity_reference) @conceal (#eq? @conceal "<") (#set! conceal "<")) +((entity_reference) @conceal (#eq? @conceal ">") (#set! conceal ">")) +((entity_reference) @conceal (#eq? @conceal "&") (#set! conceal "&")) +((entity_reference) @conceal (#eq? @conceal """) (#set! conceal "\"")) +((entity_reference) @conceal (#any-of? @conceal " " " ") (#set! conceal " ")) diff --git a/runtime/syntax/kotlin.vim b/runtime/syntax/kotlin.vim new file mode 100644 index 0000000000..9b85b8ef5c --- /dev/null +++ b/runtime/syntax/kotlin.vim @@ -0,0 +1,157 @@ +" Vim syntax file +" Language: Kotlin +" Maintainer: Alexander Udalov +" URL: https://github.com/udalov/kotlin-vim +" Last Change: 30 December 2022 + +if exists('b:current_syntax') + finish +endif + +syn keyword ktStatement break continue return +syn keyword ktConditional if else when +syn keyword ktRepeat do for while +syn keyword ktOperator in is by +syn keyword ktKeyword get set out super this where +syn keyword ktException try catch finally throw + +syn keyword ktInclude import package + +" Generated stdlib class names {{{ +" The following is generated by https://github.com/udalov/kotlin-vim/blob/master/extra/generate-stdlib-class-names.main.kts +syn keyword ktType AbstractCollection AbstractCoroutineContextElement AbstractCoroutineContextKey AbstractDoubleTimeSource AbstractIterator AbstractList AbstractLongTimeSource +syn keyword ktType AbstractMap AbstractMutableCollection AbstractMutableList AbstractMutableMap AbstractMutableSet AbstractSet AccessDeniedException Accessor Annotation +syn keyword ktType AnnotationRetention AnnotationTarget Any Appendable ArithmeticException Array ArrayDeque ArrayList AssertionError Boolean BooleanArray BooleanIterator +syn keyword ktType BuilderInference Byte ByteArray ByteIterator CName CallsInPlace CancellationException Char CharArray CharCategory CharDirectionality CharIterator CharProgression +syn keyword ktType CharRange CharSequence CharacterCodingException Charsets ClassCastException Cloneable ClosedFloatingPointRange ClosedRange Collection Comparable +syn keyword ktType ComparableTimeMark Comparator ConcurrentModificationException ConditionalEffect ContextFunctionTypeParams Continuation ContinuationInterceptor ContractBuilder +syn keyword ktType CopyActionContext CopyActionResult CoroutineContext DeepRecursiveFunction DeepRecursiveScope Delegates Deprecated DeprecatedSinceKotlin DeprecationLevel +syn keyword ktType Destructured Double DoubleArray DoubleIterator DslMarker Duration DurationUnit Effect Element EmptyCoroutineContext Entry Enum EnumEntries Error Exception +syn keyword ktType ExperimentalContracts ExperimentalJsExport ExperimentalMultiplatform ExperimentalObjCName ExperimentalObjCRefinement ExperimentalPathApi ExperimentalStdlibApi +syn keyword ktType ExperimentalSubclassOptIn ExperimentalTime ExperimentalTypeInference ExperimentalUnsignedTypes ExtensionFunctionType FileAlreadyExistsException +syn keyword ktType FileSystemException FileTreeWalk FileVisitorBuilder FileWalkDirection Float FloatArray FloatIterator FreezingIsDeprecated Function Function0 Function1 Function10 +syn keyword ktType Function11 Function12 Function13 Function14 Function15 Function16 Function17 Function18 Function19 Function2 Function20 Function21 Function22 Function3 Function4 +syn keyword ktType Function5 Function6 Function7 Function8 Function9 FunctionN Getter Grouping HashMap HashSet HiddenFromObjC HidesFromObjC Ignore IllegalArgumentException +syn keyword ktType IllegalStateException IndexOutOfBoundsException IndexedValue Int IntArray IntIterator IntProgression IntRange InvocationKind Iterable Iterator JsExport JsName +syn keyword ktType JvmDefault JvmDefaultWithCompatibility JvmDefaultWithoutCompatibility JvmField JvmInline JvmMultifileClass JvmName JvmOverloads JvmRecord JvmSerializableLambda +syn keyword ktType JvmStatic JvmSuppressWildcards JvmSynthetic JvmWildcard KAnnotatedElement KCallable KClass KClassifier KDeclarationContainer KFunction KMutableProperty +syn keyword ktType KMutableProperty0 KMutableProperty1 KMutableProperty2 KParameter KProperty KProperty0 KProperty1 KProperty2 KType KTypeParameter KTypeProjection KVariance +syn keyword ktType KVisibility Key Kind KotlinNullPointerException KotlinReflectionNotSupportedError KotlinVersion Lazy LazyThreadSafetyMode Level LinkedHashMap LinkedHashSet List +syn keyword ktType ListIterator Long LongArray LongIterator LongProgression LongRange Map MatchGroup MatchGroupCollection MatchNamedGroupCollection MatchResult Metadata Monotonic +syn keyword ktType MustBeDocumented MutableCollection MutableEntry MutableIterable MutableIterator MutableList MutableListIterator MutableMap MutableSet NoSuchElementException +syn keyword ktType NoSuchFileException NoWhenBranchMatchedException NotImplementedError Nothing NullPointerException Number NumberFormatException ObjCName ObservableProperty +syn keyword ktType OnErrorAction OnErrorResult OpenEndRange OptIn OptionalExpectation OverloadResolutionByLambdaReturnType Pair ParameterName PathWalkOption +syn keyword ktType PropertyDelegateProvider PublishedApi PurelyImplements Random RandomAccess ReadOnlyProperty ReadWriteProperty RefinesInSwift Regex RegexOption Repeatable +syn keyword ktType ReplaceWith RequiresOptIn RestrictsSuspension Result Retention Returns ReturnsNotNull RuntimeException Sequence SequenceScope Set Setter SharedImmutable Short +syn keyword ktType ShortArray ShortIterator ShouldRefineInSwift SimpleEffect SinceKotlin Strictfp String StringBuilder SubclassOptInRequired Suppress Synchronized Target +syn keyword ktType TestTimeSource ThreadLocal Throwable Throws TimeMark TimeSource TimedValue Transient Triple TypeCastException Typography UByte UByteArray UInt UIntArray +syn keyword ktType UIntProgression UIntRange ULong ULongArray ULongProgression ULongRange UShort UShortArray UninitializedPropertyAccessException Unit UnsafeVariance +syn keyword ktType UnsupportedOperationException ValueTimeMark Volatile WithComparableMarks +" }}} + +syn keyword ktModifier annotation companion enum inner abstract final open override sealed vararg dynamic expect actual suspend +syn keyword ktStructure class object interface typealias fun val var constructor init + +syn keyword ktReservedKeyword typeof + +syn keyword ktBoolean true false +syn keyword ktConstant null + +syn keyword ktModifier reified external inline noinline crossinline + +syn match ktModifier "\v<data>\ze\@=.*<(class|object)>" +syn match ktModifier "\v<value>\ze\@=.*<class>" +syn match ktModifier "\v<(tailrec|operator|infix)>\ze\@=.*<fun>" +syn match ktModifier "\v<const>\ze\@=.*<val>" +syn match ktModifier "\v<lateinit>\ze\@=.*<var>" +syn match ktModifier "\v<(internal|private|protected|public)>\ze\@=.*<(class|object|interface|typealias|fun|val|var|constructor|get|set)>" + +syn match ktOperator "\v\?:|::|\<\=? | \>\=?|[!=]\=\=?|<as>\??|[-*+/%]\=?|[!&|]" + +syn keyword ktTodo TODO FIXME XXX contained +syn match ktShebang "\v^#!.*$" +syn match ktLineComment "\v//.*$" contains=ktTodo,@Spell +syn region ktComment matchgroup=ktCommentMatchGroup start="/\*" end="\*/" contains=ktComment,ktTodo,@Spell + +syn region ktDocComment start="/\*\*" end="\*/" contains=ktDocTag,ktTodo,@Spell +syn match ktDocTag "\v\@(author|constructor|receiver|return|since|suppress)>" contained +syn match ktDocTag "\v\@(exception|param|property|throws|see|sample)>\s*\S+" contains=ktDocTagParam contained +syn match ktDocTagParam "\v(\s|\[)\S+" contained +syn match ktComment "/\*\*/" + +syn match ktSpecialCharError "\v\\." contained +syn match ktSpecialChar "\v\\([tbnr'"$\\]|u\x{4})" contained +syn region ktString start='"' skip='\\"' end='"' contains=ktSimpleInterpolation,ktComplexInterpolation,ktSpecialChar,ktSpecialCharError,@Spell +syn region ktString start='"""' end='""""*' contains=ktSimpleInterpolation,ktComplexInterpolation,@Spell +syn match ktCharacter "\v'[^']*'" contains=ktSpecialChar,ktSpecialCharError +syn match ktCharacter "\v'\\''" contains=ktSpecialChar +syn match ktCharacter "\v'[^\\]'" + +syn match ktAnnotation "\v(\w)@<!\@[[:alnum:]_.]*(:[[:alnum:]_.]*)?" +syn match ktLabel "\v\w+\@" +syn match ktLabel "\v(\w)@<=\@\w+" + +syn match ktSimpleInterpolation "\v\$\h\w*" contained +syn region ktComplexInterpolation matchgroup=ktComplexInterpolationBrace start="\v\$\{" end="\v\}" contains=ALLBUT,ktSimpleInterpolation,ktTodo,ktSpecialCharError,ktSpecialChar,ktDocTag,ktDocTagParam + +syn match ktNumber "\v<\d+[_[:digit:]]*(uL?|UL?|[LFf])?" +syn match ktNumber "\v<0[Xx]\x+[_[:xdigit:]]*(uL?|UL?|L)?" +syn match ktNumber "\v<0[Bb][01]+[_01]*(uL?|UL?|L)?" +syn match ktFloat "\v<\d*(\d[eE][-+]?\d+|\.\d+([eE][-+]?\d+)?)[Ff]?" + +syn match ktEscapedName "\v`.*`" + +syn match ktExclExcl "!!" +syn match ktArrow "->" + +syn region ktFold start="{" end="}" transparent fold + +exec "syntax sync ccomment ktComment minlines=10" + +hi def link ktStatement Statement +hi def link ktConditional Conditional +hi def link ktRepeat Repeat +hi def link ktOperator Operator +hi def link ktKeyword Keyword +hi def link ktException Exception +hi def link ktReservedKeyword Error + +hi def link ktInclude Include + +hi def link ktType Type +hi def link ktModifier StorageClass +hi def link ktStructure Structure +hi def link ktTypedef Typedef + +hi def link ktBoolean Boolean +hi def link ktConstant Constant + +hi def link ktTodo Todo +hi def link ktShebang Comment +hi def link ktLineComment Comment +hi def link ktComment Comment +hi def link ktCommentMatchGroup Comment +hi def link ktDocComment Comment +hi def link ktDocTag Special +hi def link ktDocTagParam Identifier + +hi def link ktSpecialChar SpecialChar +hi def link ktSpecialCharError Error +hi def link ktString String +hi def link ktCharacter Character + +hi def link ktAnnotation Identifier +hi def link ktLabel Identifier + +hi def link ktSimpleInterpolation Identifier +hi def link ktComplexInterpolationBrace Identifier + +hi def link ktNumber Number +hi def link ktFloat Float + +hi def link ktExclExcl Special +hi def link ktArrow Structure + +let b:current_syntax = 'kotlin' + +" vim:foldmethod=marker diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index 7594a0e7df..1c8bc5a3cb 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -361,6 +361,12 @@ local function process_line(line, in_stream, generics) return process_block_comment(line:sub(5), in_stream) end + -- Hax... I'm sorry + -- M.fun = vim.memoize(function(...) + -- -> + -- function M.fun(...) + line = line:gsub('^(.+) = .*_memoize%([^,]+, function%((.*)%)$', 'function %1(%2)') + if line:find('^function') or line:find('^local%s+function') then return process_function_header(line) end diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 82a88c6774..db79de63cc 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -230,6 +230,11 @@ if(WIN32) endif() elseif(APPLE) target_link_libraries(nvim PRIVATE "-framework CoreServices") + + # Actually export symbols - symbols may not be visible even though + # ENABLE_EXPORTS is set to true. See + # https://github.com/neovim/neovim/issues/25295 + set_target_properties(nvim PROPERTIES LINK_FLAGS "-Wl,-export_dynamic") endif() if(UNIX) @@ -879,7 +884,13 @@ set(VIMDOC_FILES ) glob_wrapper(API_SOURCES ${PROJECT_SOURCE_DIR}/src/nvim/api/*.c) -glob_wrapper(LUA_SOURCES ${NVIM_RUNTIME_DIR}/lua/vim/*.lua) + +glob_wrapper(LUA_SOURCES + ${NVIM_RUNTIME_DIR}/lua/vim/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/filetype/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/lsp/*.lua + ${NVIM_RUNTIME_DIR}/lua/vim/treesitter/*.lua +) add_custom_command( OUTPUT ${VIMDOC_FILES} diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index f5a77ece7e..f345edc52c 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -518,6 +518,7 @@ static int64_t convert_index(int64_t index) /// @return Option Information Dictionary nvim_get_option_info(String name, Error *err) FUNC_API_SINCE(7) + FUNC_API_DEPRECATED_SINCE(11) { return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err); } @@ -531,6 +532,7 @@ Dictionary nvim_get_option_info(String name, Error *err) /// @param[out] err Error details, if any void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err); } @@ -543,6 +545,7 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) /// @return Option value (global) Object nvim_get_option(String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { return get_option_from(NULL, SREQ_GLOBAL, name, err); } @@ -556,6 +559,7 @@ Object nvim_get_option(String name, Arena *arena, Error *err) /// @return Option value Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -577,6 +581,7 @@ Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) /// @param[out] err Error details, if any void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -596,6 +601,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object /// @return Option value Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { win_T *win = find_window_by_handle(window, err); @@ -617,6 +623,7 @@ Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) /// @param[out] err Error details, if any void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(11) { win_T *win = find_window_by_handle(window, err); diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index faab6e593c..91e197bea7 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -113,6 +113,36 @@ static Object hl_group_name(int hl_id, bool hl_name) } } +Array virt_text_to_array(VirtText vt, bool hl_name) +{ + Array chunks = ARRAY_DICT_INIT; + Array hl_array = ARRAY_DICT_INIT; + for (size_t i = 0; i < kv_size(vt); i++) { + char *text = kv_A(vt, i).text; + int hl_id = kv_A(vt, i).hl_id; + if (text == NULL) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + continue; + } + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, CSTR_TO_OBJ(text)); + if (hl_array.size > 0) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + ADD(chunk, ARRAY_OBJ(hl_array)); + hl_array = (Array)ARRAY_DICT_INIT; + } else if (hl_id > 0) { + ADD(chunk, hl_group_name(hl_id, hl_name)); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + assert(hl_array.size == 0); + return chunks; +} + static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict, bool hl_name) { Array rv = ARRAY_DICT_INIT; @@ -145,16 +175,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict } if (kv_size(decor->virt_text)) { - Array chunks = ARRAY_DICT_INIT; - for (size_t i = 0; i < decor->virt_text.size; i++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &decor->virt_text.items[i]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + Array chunks = virt_text_to_array(decor->virt_text, hl_name); PUT(dict, "virt_text", ARRAY_OBJ(chunks)); PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); if (decor->virt_text_pos == kVTWinCol) { @@ -171,19 +192,9 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict if (kv_size(decor->virt_lines)) { Array all_chunks = ARRAY_DICT_INIT; bool virt_lines_leftcol = false; - for (size_t i = 0; i < decor->virt_lines.size; i++) { - Array chunks = ARRAY_DICT_INIT; - VirtText *vt = &decor->virt_lines.items[i].line; - virt_lines_leftcol = decor->virt_lines.items[i].left_col; - for (size_t j = 0; j < vt->size; j++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &vt->items[j]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { + virt_lines_leftcol = kv_A(decor->virt_lines, i).left_col; + Array chunks = virt_text_to_array(kv_A(decor->virt_lines, i).line, hl_name); ADD(all_chunks, ARRAY_OBJ(chunks)); } PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); @@ -1188,8 +1199,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) goto free_exit; } if (j < arr.size - 1) { - kv_push(virt_text, ((VirtTextChunk){ .text = NULL, - .hl_id = hl_id })); + kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id })); } } } else { diff --git a/src/nvim/api/keysets.h b/src/nvim/api/keysets.h index a98cbe276f..236e75983e 100644 --- a/src/nvim/api/keysets.h +++ b/src/nvim/api/keysets.h @@ -112,6 +112,7 @@ typedef struct { String footer_pos; String style; Boolean noautocmd; + Boolean fixed; } Dict(float_config); typedef struct { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8fcabb3605..5463578b56 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -21,8 +21,10 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_eval.h" #include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/map.h" @@ -235,8 +237,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva // Delete the key if (di == NULL) { // Doesn't exist, fail - api_set_error(err, kErrorTypeValidation, "Key not found: %s", - key.data); + api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); } else { // Notify watchers if (watched) { @@ -265,13 +266,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { - if (watched) { - tv_copy(&di->di_tv, &oldtv); - } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); } + bool type_error = false; + if (dict == &vimvardict + && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { + tv_clear(&tv); + if (type_error) { + api_set_error(err, kErrorTypeValidation, + "Setting v:%s to value with wrong type", key.data); + } + return rv; + } + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } tv_clear(&di->di_tv); } diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 70c97be984..0ea2310042 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -833,8 +833,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int bool was_space = false; for (size_t i = 0; i < ncells; i++) { repeat++; - if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || strcmp(chunk[i], chunk[i + 1]) != 0) { + if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. @@ -859,7 +858,9 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1); nelem++; mpack_array(buf, csize); - mpack_str(buf, chunk[i]); + char sc_buf[MAX_SCHAR_SIZE]; + schar_get(sc_buf, chunk[i]); + mpack_str(buf, sc_buf); if (csize >= 2) { mpack_uint(buf, (uint32_t)attrs[i]); if (csize >= 3) { @@ -869,7 +870,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int data->ncells_pending += MIN(repeat, 2); last_hl = attrs[i]; repeat = 0; - was_space = strequal(chunk[i], " "); + was_space = chunk[i] == schar_from_ascii(' '); } } // If the last chunk was all spaces, add a clearing chunk even if there are @@ -893,8 +894,10 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int for (int i = 0; i < endcol - startcol; i++) { remote_ui_cursor_goto(ui, row, startcol + i); remote_ui_highlight_set(ui, attrs[i]); - remote_ui_put(ui, chunk[i]); - if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) { + char sc_buf[MAX_SCHAR_SIZE]; + schar_get(sc_buf, chunk[i]); + remote_ui_put(ui, sc_buf); + if (utf_ambiguous_width(utf_ptr2char(sc_buf))) { data->client_col = -1; // force cursor update } } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index fc70215352..6ca5024a04 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -167,4 +167,7 @@ void msg_history_show(Array entries) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void msg_history_clear(void) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; + +void error_exit(Integer status) + FUNC_API_SINCE(12); #endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9d08e9b6c7..00641c633d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1442,7 +1442,7 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|, /// values are booleans (default false). Also: -/// - "noremap" non-recursive mapping |:noremap| +/// - "noremap" disables |recursive_mapping|, like |:noremap| /// - "desc" human-readable description. /// - "callback" Lua function called in place of {rhs}. /// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the @@ -1710,6 +1710,8 @@ static void write_msg(String message, bool to_err, bool writeln) msg_didout = true; \ kv_drop(line_buf, kv_size(line_buf)); \ kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ + } else if (c == NUL) { \ + kv_push(line_buf, NL); \ } else { \ kv_push(line_buf, c); \ } @@ -1948,7 +1950,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E } ret = arena_array(arena, 3); size_t off = g->line_offset[(size_t)row] + (size_t)col; - ADD_C(ret, CSTR_AS_OBJ((char *)g->chars[off])); + char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false); + schar_get(sc_buf, g->chars[off]); + ADD_C(ret, CSTR_AS_OBJ(sc_buf)); int attr = g->attrs[off]; ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err))); // will not work first time @@ -1964,6 +1968,11 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } +void nvim__invalidate_glyph_cache(void) +{ + schar_cache_clear_force(); +} + Object nvim__unpack(String str, Error *err) FUNC_API_FAST { diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 63cf3bb701..ea0b7ce512 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -16,7 +16,7 @@ #include "nvim/drawscreen.h" #include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" #include "nvim/highlight_group.h" #include "nvim/macros.h" #include "nvim/mbyte.h" @@ -163,6 +163,8 @@ /// - noautocmd: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. +/// - fixed: If true when anchor is NW or SW, the float window +/// would be kept fixed even if the window would be truncated. /// /// @param[out] err Error details, if any /// @@ -195,6 +197,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (win_valid(wp) && buffer > 0) { Boolean noautocmd = !enter || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); + if (!fconfig.noautocmd) { + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf); + } } if (!win_valid(wp)) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); @@ -254,34 +259,28 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, BorderTextType bordertext_type) { - VirtText chunks; + VirtText vt; AlignTextPos align; char *field_name; char *field_pos_name; switch (bordertext_type) { case kBorderTextTitle: - chunks = fconfig->title_chunks; + vt = fconfig->title_chunks; align = fconfig->title_pos; field_name = "title"; field_pos_name = "title_pos"; break; case kBorderTextFooter: - chunks = fconfig->footer_chunks; + vt = fconfig->footer_chunks; align = fconfig->footer_pos; field_name = "footer"; field_pos_name = "footer_pos"; break; + default: + abort(); } - Array bordertext = ARRAY_DICT_INIT; - for (size_t i = 0; i < chunks.size; i++) { - Array tuple = ARRAY_DICT_INIT; - ADD(tuple, CSTR_TO_OBJ(chunks.items[i].text)); - if (chunks.items[i].hl_id > 0) { - ADD(tuple, CSTR_TO_OBJ(syn_id2name(chunks.items[i].hl_id))); - } - ADD(bordertext, ARRAY_OBJ(tuple)); - } + Array bordertext = virt_text_to_array(vt, true); PUT(config, field_name, ARRAY_OBJ(bordertext)); char *pos; @@ -348,7 +347,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) for (size_t i = 0; i < 8; i++) { Array tuple = ARRAY_DICT_INIT; - String s = cstrn_to_string(config->border_chars[i], sizeof(schar_T)); + String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE); int hi_id = config->border_hl_ids[i]; char *hi_name = syn_id2name(hi_id); @@ -520,7 +519,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { struct { const char *name; - schar_T chars[8]; + char chars[8][MAX_SCHAR_SIZE]; bool shadow_color; } defaults[] = { { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, @@ -531,7 +530,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { NULL, { { NUL } }, false }, }; - schar_T *chars = fconfig->border_chars; + char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars; int *hl_ids = fconfig->border_hl_ids; fconfig->border = true; @@ -845,6 +844,10 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, fconfig->noautocmd = config->noautocmd; } + if (HAS_KEY_X(config, fixed)) { + fconfig->fixed = config->fixed; + } + return true; #undef HAS_KEY_X } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index 74348052b0..9c2b3ba6d8 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -857,12 +857,17 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) while (true) { win_T *wpnext = NULL; tabpage_T *tpnext = curtab->tp_next; - for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { + // Try to close floating windows first + for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) { int i; - wpnext = wp->w_next; + wpnext = wp->w_floating + ? wp->w_prev->w_floating ? wp->w_prev : firstwin + : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next; buf_T *buf = wp->w_buffer; if (buf->b_ffname == NULL - || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { + || (!aall->keep_tabs + && (buf->b_nwindows > 1 || wp->w_width != Columns + || (wp->w_floating && !is_aucmd_win(wp))))) { i = aall->opened_len; } else { // check if the buffer in this window is in the arglist @@ -917,7 +922,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) (void)autowrite(buf, false); // Check if autocommands removed the window. if (!win_valid(wp) || !bufref_valid(&bufref)) { - wpnext = firstwin; // Start all over... + wpnext = lastwin->w_floating ? lastwin : firstwin; // Start all over... continue; } } @@ -930,7 +935,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... - wpnext = firstwin; + wpnext = lastwin->w_floating ? lastwin : firstwin; } } } @@ -977,6 +982,8 @@ static void arg_all_open_windows(arg_all_state_T *aall, int count) if (aall->keep_tabs) { aall->new_curwin = wp; aall->new_curtab = curtab; + } else if (wp->w_floating) { + break; } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { emsg(_(e_window_layout_changed_unexpectedly)); i = count; @@ -1098,7 +1105,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs) autocmd_no_leave++; last_curwin = curwin; last_curtab = curtab; - win_enter(lastwin, false); + // lastwin may be aucmd_win + win_enter(lastwin_nofloating(), false); // Open up to "count" windows. arg_all_open_windows(&aall, count); diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3fa20f4e48..ff0c2f063f 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1341,7 +1341,6 @@ void aucmd_restbuf(aco_save_T *aco) if (aco->use_aucmd_win_idx >= 0) { win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; - curbuf->b_nwindows--; // Find "awp", it can't be closed, but it may be in another tab page. // Do not trigger autocommands here. block_autocmds(); @@ -1357,7 +1356,7 @@ void aucmd_restbuf(aco_save_T *aco) } } win_found: - ; + curbuf->b_nwindows--; const bool save_stop_insert_mode = stop_insert_mode; // May need to stop Insert mode if we were in a prompt buffer. leaving_window(curwin); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index d2a5eab0a5..7a3e65e10e 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3182,7 +3182,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) (curbuf->b_flags & BF_NOTEDITED) && !dontwrite ? _("[Not edited]") : "", (curbuf->b_flags & BF_NEW) && !dontwrite - ? new_file_message() : "", + ? _("[New]") : "", (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", curbuf->b_p_ro @@ -3221,7 +3221,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); } - (void)append_arg_number(curwin, buffer, IOSIZE, !shortmess(SHM_FILE)); + (void)append_arg_number(curwin, buffer, IOSIZE); if (dont_truncate) { // Temporarily set msg_scroll to avoid the message being truncated. @@ -3372,7 +3372,7 @@ void maketitle(void) *buf_p = NUL; } - append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false); + append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf))); xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf))); @@ -3509,15 +3509,14 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) } } -/// Append (file 2 of 8) to "buf[buflen]", if editing more than one file. +/// Append (2 of 8) to "buf[buflen]", if editing more than one file. /// /// @param wp window whose buffers to check /// @param[in,out] buf string buffer to add the text to /// @param buflen length of the string buffer -/// @param add_file if true, add "file" before the arg number /// /// @return true if it was appended. -bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) +bool append_arg_number(win_T *wp, char *buf, int buflen) FUNC_ATTR_NONNULL_ALL { // Nothing to do @@ -3525,17 +3524,7 @@ bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) return false; } - const char *msg; - switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0)) { - case 0: - msg = _(" (%d of %d)"); break; - case 1: - msg = _(" ((%d) of %d)"); break; - case 2: - msg = _(" (file %d of %d)"); break; - case 3: - msg = _(" (file (%d) of %d)"); break; - } + const char *msg = wp->w_arg_idx_invalid ? _(" ((%d) of %d)") : _(" (%d of %d)"); char *p = buf + strlen(buf); // go to the end of the buffer vim_snprintf(p, (size_t)(buflen - (p - buf)), msg, wp->w_arg_idx + 1, ARGCOUNT); @@ -3616,21 +3605,28 @@ void ex_buffer_all(exarg_T *eap) } while (true) { tpnext = curtab->tp_next; - for (wp = firstwin; wp != NULL; wp = wpnext) { - wpnext = wp->w_next; + // Try to close floating windows first + for (wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) { + wpnext = wp->w_floating + ? wp->w_prev->w_floating ? wp->w_prev : firstwin + : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next; if ((wp->w_buffer->b_nwindows > 1 + || wp->w_floating || ((cmdmod.cmod_split & WSP_VERT) ? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch - tabline_height() - global_stl_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) && !ONE_WINDOW - && !(wp->w_closing - || wp->w_buffer->b_locked > 0)) { - win_close(wp, false, false); - wpnext = firstwin; // just in case an autocommand does - // something strange with windows - tpnext = first_tabpage; // start all over... + && !(wp->w_closing || wp->w_buffer->b_locked > 0) + && !is_aucmd_win(wp)) { + if (win_close(wp, false, false) == FAIL) { + break; + } + // Just in case an autocommand does something strange with + // windows: start all over... + wpnext = lastwin->w_floating ? lastwin : firstwin; + tpnext = first_tabpage; open_wins = 0; } else { open_wins++; @@ -3650,7 +3646,8 @@ void ex_buffer_all(exarg_T *eap) // // Don't execute Win/Buf Enter/Leave autocommands here. autocmd_no_enter++; - win_enter(lastwin, false); + // lastwin may be aucmd_win + win_enter(lastwin_nofloating(), false); autocmd_no_leave++; for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) { // Check if this buffer needs a window @@ -3668,7 +3665,7 @@ void ex_buffer_all(exarg_T *eap) } else { // Check if this buffer already has a window for (wp = firstwin; wp != NULL; wp = wp->w_next) { - if (wp->w_buffer == buf) { + if (!wp->w_floating && wp->w_buffer == buf) { break; } } @@ -3742,7 +3739,7 @@ void ex_buffer_all(exarg_T *eap) // Close superfluous windows. for (wp = lastwin; open_wins > count;) { r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, false) == OK); + || autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp); if (!win_valid(wp)) { // BufWrite Autocommands made the window invalid, start over wp = lastwin; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3f55dbbc00..ff0fca1a56 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -954,7 +954,7 @@ typedef struct { WinStyle style; bool border; bool shadow; - schar_T border_chars[8]; + char border_chars[8][MAX_SCHAR_SIZE]; int border_hl_ids[8]; int border_attr[8]; bool title; @@ -966,6 +966,7 @@ typedef struct { VirtText footer_chunks; int footer_width; bool noautocmd; + bool fixed; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -975,7 +976,8 @@ typedef struct { .focusable = true, \ .zindex = kZIndexFloatDefault, \ .style = kWinStyleUnused, \ - .noautocmd = false }) + .noautocmd = false, \ + .fixed = false }) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). @@ -1241,6 +1243,8 @@ struct window_S { // this window, w_allbuf_opt is for all buffers in this window. winopt_T w_onebuf_opt; winopt_T w_allbuf_opt; + // transform a pointer to a "onebuf" option into a "allbuf" option +#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) // A few options have local flags for P_INSECURE. uint32_t w_p_stl_flags; // flags for 'statusline' @@ -1256,9 +1260,6 @@ struct window_S { int w_briopt_list; // additional indent for lists int w_briopt_vcol; // indent for specific column - // transform a pointer to a "onebuf" option into a "allbuf" option -#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) - long w_scbind_pos; ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary. diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 445e946543..0c95314c52 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1766,11 +1766,11 @@ restore_backup: xstrlcat(IObuff, _("[Device]"), IOSIZE); insert_space = true; } else if (newfile) { - xstrlcat(IObuff, new_file_message(), IOSIZE); + xstrlcat(IObuff, _("[New]"), IOSIZE); insert_space = true; } if (no_eol) { - msg_add_eol(); + xstrlcat(IObuff, _("[noeol]"), IOSIZE); insert_space = true; } // may add [unix/dos/mac] diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 9470f77ab5..1e088ec7fc 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -601,10 +601,10 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid; - grid_puts(grid, buf, row, 0, attr); + grid_puts(grid, buf, -1, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + grid_puts(grid, selstart, -1, row, selstart_col, HL_ATTR(HLF_WM)); } grid_fill(grid, row, row + 1, clen, Columns, diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f4ca31040a..9d391cde8c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -153,6 +153,24 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +/// Get the next chunk of a virtual text item. +/// +/// @param[in] vt The virtual text item +/// @param[in,out] pos Position in the virtual text item +/// @param[in,out] attr Highlight attribute +/// +/// @return The text of the chunk, or NULL if there are no more chunks +char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) +{ + char *text = NULL; + for (; text == NULL && *pos < kv_size(vt); (*pos)++) { + text = kv_A(vt, *pos).text; + int hl_id = kv_A(vt, *pos).hl_id; + *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0); + } + return text; +} + Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) { MarkTreeIter itr[1] = { 0 }; @@ -254,7 +272,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r DecorRange range = { start_row, start_col, end_row, end_col, *decor, attr_id, - kv_size(decor->virt_text) && owned, -1, ns_id, mark_id }; + kv_size(decor->virt_text) && owned, -10, ns_id, mark_id }; kv_pushp(state->active); size_t index; @@ -268,10 +286,10 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } -/// Initialize the draw_col of a newly-added non-inline virtual text item. +/// Initialize the draw_col of a newly-added virtual text item. static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { - if (win_col < 0) { + if (win_col < 0 && item->decor.virt_text_pos != kVTInline) { item->draw_col = win_col; } else if (item->decor.virt_text_pos == kVTOverlay) { item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; @@ -371,8 +389,7 @@ next_mark: spell = item.decor.spell; } if (item.start_row == state->row && item.start_col <= col - && decor_virt_pos(&item.decor) && item.draw_col == -1 - && item.decor.virt_text_pos != kVTInline) { + && decor_virt_pos(&item.decor) && item.draw_col == -10) { decor_init_draw_col(win_col, hidden, &item); } if (keep) { diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 0f191aa870..fef5ff7c2a 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -85,6 +85,7 @@ typedef struct { /// Screen column to draw the virtual text. /// When -1, the virtual text may be drawn after deciding where. /// When -3, the virtual text should be drawn on the next screen line. + /// When -10, the virtual text has just been added. /// When INT_MIN, the virtual text should no longer be drawn. int draw_col; uint64_t ns_id; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 811cfc1eb2..1e5798db32 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -106,6 +106,8 @@ typedef struct { int c_extra; ///< extra chars, all the same int c_final; ///< final char, mandatory if set + int n_closing; ///< number of chars in fdc which will be closing + bool extra_for_extmark; ///< n_extra set for inline virtual text // saved "extra" items for when draw_state becomes WL_LINE (again) @@ -221,11 +223,11 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b if (*p == TAB) { cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); for (int c = 0; c < cells; c++) { - schar_from_ascii(dest[c], ' '); + dest[rl ? -c : c] = schar_from_ascii(' '); } goto done; } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], *p); + dest[0] = schar_from_ascii(*p); s->prev_c = u8c; } else { if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { @@ -252,10 +254,10 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b } else { s->prev_c = u8c; } - schar_from_cc(dest[0], u8c, u8cc); + dest[0] = schar_from_cc(u8c, u8cc); } if (cells > 1) { - dest[1][0] = 0; + dest[rl ? -1 : 1] = 0; } done: s->p += c_len; @@ -275,12 +277,20 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } if (item->draw_col == -1) { if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; + if (wp->w_p_rl) { + right_pos += item->decor.virt_text_width; + } else { + right_pos -= item->decor.virt_text_width; + } item->draw_col = right_pos; } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { item->draw_col = state->eol_col; } else if (item->decor.virt_text_pos == kVTWinCol) { - item->draw_col = MAX(item->decor.col + col_off, 0); + if (wp->w_p_rl) { + item->draw_col = MIN(col_off - item->decor.col, wp->w_grid.cols - 1); + } else { + item->draw_col = MAX(col_off + item->decor.col, 0); + } } } if (item->draw_col < 0) { @@ -295,42 +305,44 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } if (kv_size(item->decor.virt_text)) { col = draw_virt_text_item(buf, item->draw_col, item->decor.virt_text, - item->decor.hl_mode, max_col, item->draw_col - col_off); + item->decor.hl_mode, max_col, item->draw_col - col_off, wp->w_p_rl); } item->draw_col = INT_MIN; // deactivate if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - state->eol_col = col + 1; + if (wp->w_p_rl) { + state->eol_col = col - 1; + } else { + state->eol_col = col + 1; + } } - *end_col = MAX(*end_col, col); + if (wp->w_p_rl) { + *end_col = MIN(*end_col, col); + } else { + *end_col = MAX(*end_col, col); + } } } static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) + int vcol, bool rl) { LineState s = LINE_STATE(""); int virt_attr = 0; size_t virt_pos = 0; - while (col < max_col) { - if (!*s.p) { + while (rl ? col > max_col : col < max_col) { + if (*s.p == NUL) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { + s.p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (s.p == NULL) { break; } } - if (!*s.p) { + if (*s.p == NUL) { continue; } int attr; @@ -344,21 +356,28 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, attr = virt_attr; } schar_T dummy[2]; + bool rl_overwrote_double_width = linebuf_char[col] == 0; int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], - max_col - col, false, vcol); + rl ? col - max_col : max_col - col, rl, vcol); // If we failed to emit a char, we still need to put a space and advance. if (cells < 1) { - schar_from_ascii(linebuf_char[col], ' '); + linebuf_char[col] = schar_from_ascii(' '); cells = 1; } for (int c = 0; c < cells; c++) { - linebuf_attr[col++] = attr; + linebuf_attr[col] = attr; + if (rl) { + col--; + } else { + col++; + } } - if (col < max_col && linebuf_char[col][0] == 0) { - // If the left half of a double-width char is overwritten, - // change the right half to a space so that grid redraws properly, - // but don't advance the current column. - schar_from_ascii(linebuf_char[col], ' '); + // If one half of a double-width char is overwritten, + // change the other half to a space so that grid redraws properly, + // but don't advance the current column. + if ((rl && col > max_col && rl_overwrote_double_width) + || (!rl && col < max_col && linebuf_char[col] == 0)) { + linebuf_char[col] = schar_from_ascii(' '); } vcol += cells; } @@ -384,7 +403,8 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv) // Allocate a buffer, "wlv->extra[]" may already be in use. xfree(wlv->p_extra_free); wlv->p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1); - wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum); + wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum, + &wlv->n_closing); wlv->p_extra_free[wlv->n_extra] = NUL; wlv->p_extra = wlv->p_extra_free; wlv->c_extra = NUL; @@ -405,7 +425,7 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv) /// /// Assume monocell characters /// @return number of chars added to \param p -size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing) { int i = 0; int level; @@ -447,16 +467,23 @@ size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) } } + int n_closing_val = i; + if (closed) { if (symbol != 0) { // rollback previous write char_counter -= (size_t)len; memset(&p[char_counter], ' ', (size_t)len); + n_closing_val--; } len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); char_counter += (size_t)len; } + if (n_closing) { + *n_closing = n_closing_val; + } + return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); } @@ -917,17 +944,20 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t } } else { // already inside existing inline virtual text with multiple chunks - VirtTextChunk vtc = kv_A(wlv->virt_inline, wlv->virt_inline_i); - wlv->virt_inline_i++; - wlv->p_extra = vtc.text; - wlv->n_extra = (int)strlen(vtc.text); + int attr = 0; + char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr); + if (text == NULL) { + continue; + } + wlv->p_extra = text; + wlv->n_extra = (int)strlen(text); if (wlv->n_extra == 0) { continue; } wlv->c_extra = NUL; wlv->c_final = NUL; - wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; - wlv->n_attr = mb_charlen(vtc.text); + wlv->extra_attr = attr; + wlv->n_attr = mb_charlen(text); // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -1377,8 +1407,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl statuscol.width = win_col_off(wp) - (cmdwin_type != 0 && wp == curwin); statuscol.use_cul = use_cursor_line_highlight(wp, lnum); statuscol.sign_cul_id = statuscol.use_cul ? sign_cul.hl_id : 0; - statuscol.num_attr = sign_num.hl_id ? syn_id2attr(sign_num.hl_id) - : get_line_number_attr(wp, &wlv); + statuscol.num_attr = sign_num.hl_id > 0 ? syn_id2attr(sign_num.hl_id) : 0; } else { if (sign_cul.hl_id > 0) { sign_cul_attr = syn_id2attr(sign_cul.hl_id); @@ -1510,6 +1539,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl cts.cts_vcol += charsize; prev_ptr = cts.cts_ptr; MB_PTR_ADV(cts.cts_ptr); + if (wp->w_p_list) { + in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } else if (cts.cts_ptr >= line + leadcol + && wp->w_p_lcs_chars.multispace != NULL) { + multispace_pos++; + if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } else if (cts.cts_ptr < line + leadcol + && wp->w_p_lcs_chars.leadmultispace != NULL) { + multispace_pos++; + if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { + multispace_pos = 0; + } + } + } } wlv.vcol = cts.cts_vcol; ptr = cts.cts_ptr; @@ -1665,13 +1713,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); } } - if (!virt_line_offset) { + if (virt_line_offset == 0) { // Skip the column states if there is a "virt_left_col" line. wlv.draw_state = WL_BRI - 1; } else if (statuscol.draw) { // Skip fold, sign and number states if 'statuscolumn' is set. wlv.draw_state = WL_STC - 1; } + if (virt_line_offset >= 0 && wp->w_p_rl) { + virt_line_offset = wp->w_grid.cols - 1 - virt_line_offset; + } } if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) { @@ -1704,6 +1755,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.draw_state = WL_STC; // Draw the 'statuscolumn' if option is set. if (statuscol.draw) { + if (sign_num.hl_id == 0) { + statuscol.num_attr = get_line_number_attr(wp, &wlv); + } if (statuscol.textp == NULL) { v = (ptr - line); get_statuscol_str(wp, lnum, wlv.row - startrow - wlv.filler_lines, &statuscol); @@ -1759,7 +1813,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && wlv.vcol >= wp->w_virtcol) || (number_only && wlv.draw_state > WL_STC)) && wlv.filler_todo <= 0) { - draw_virt_text(wp, buf, win_col_offset, &wlv.col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); grid_put_linebuf(grid, wlv.row, 0, wlv.col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. @@ -2332,9 +2386,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } } - in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); - if (!in_multispace) { - multispace_pos = 0; + if (wp->w_p_list) { + in_multispace = c == ' ' && (*ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } } // 'list': Change char 160 to 'nbsp' and space to 'space'. @@ -2737,7 +2794,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.col += n; } else { // Add a blank character to highlight. - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); } if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among @@ -2795,7 +2852,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl ? 1 : 0); if (has_decor) { - has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); + has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, + wlv.col + (wp->w_p_rl ? -eol_skip : eol_skip)); } if (((wp->w_p_cuc @@ -2839,7 +2897,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl int col_stride = wp->w_p_rl ? -1 : 1; while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) { - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); linebuf_vcol[wlv.off] = MAXCOL; wlv.col += col_stride; if (draw_color_col) { @@ -2873,7 +2931,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl // logical line int n = wp->w_p_rl ? -1 : 1; while (wlv.col >= 0 && wlv.col < grid->cols) { - schar_from_ascii(linebuf_char[wlv.off], ' '); + linebuf_char[wlv.off] = schar_from_ascii(' '); linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol]; linebuf_vcol[wlv.off] = wlv.vcol; wlv.off += n; @@ -2883,9 +2941,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } if (kv_size(fold_vt) > 0) { - draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0); + draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, + wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl); } - draw_virt_text(wp, buf, win_col_offset, &wlv.col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); grid_put_linebuf(grid, wlv.row, 0, wlv.col, grid->cols, wp->w_p_rl, wp, bg_attr, false); wlv.row++; @@ -2973,9 +3032,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.col--; } if (mb_utf8) { - schar_from_cc(linebuf_char[wlv.off], mb_c, u8cc); + linebuf_char[wlv.off] = schar_from_cc(mb_c, u8cc); } else { - schar_from_ascii(linebuf_char[wlv.off], (char)c); + linebuf_char[wlv.off] = schar_from_ascii((char)c); } if (multi_attr) { linebuf_attr[wlv.off] = multi_attr; @@ -2984,26 +3043,39 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl linebuf_attr[wlv.off] = wlv.char_attr; } - linebuf_vcol[wlv.off] = wlv.vcol; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { + linebuf_vcol[wlv.off] = wlv.vcol; + } else if (wlv.draw_state == WL_FOLD) { + if (wlv.n_closing > 0) { + linebuf_vcol[wlv.off] = -3; + wlv.n_closing--; + } else { + linebuf_vcol[wlv.off] = -2; + } + } else { + linebuf_vcol[wlv.off] = -1; + } if (utf_char2cells(mb_c) > 1) { // Need to fill two screen columns. wlv.off++; wlv.col++; // UTF-8: Put a 0 in the second screen char. - linebuf_char[wlv.off][0] = 0; + linebuf_char[wlv.off] = 0; linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1]; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { - wlv.vcol++; + linebuf_vcol[wlv.off] = ++wlv.vcol; + } else { + linebuf_vcol[wlv.off] = -1; } + // When "wlv.tocol" is halfway through a character, set it to the end // of the character, otherwise highlighting won't stop. if (wlv.tocol == wlv.vcol) { wlv.tocol++; } - linebuf_vcol[wlv.off] = wlv.vcol; - if (wp->w_p_rl) { // now it's time to backup one cell wlv.off--; @@ -3141,9 +3213,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl int draw_col = wlv.col - wlv.boguscols; if (virt_line_offset >= 0) { draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, - kHlModeReplace, grid->cols, 0); + kHlModeReplace, wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl); } else if (wlv.filler_todo <= 0) { - draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, wlv.row); + draw_virt_text(wp, buf, win_col_offset, &draw_col, wp->w_p_rl ? -1 : grid->cols, wlv.row); } grid_put_linebuf(grid, wlv.row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap); @@ -3154,9 +3226,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl // Force a redraw of the first column of the next line. current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; - - // Remember that the line wraps, used for modeless copy. - current_grid->line_wraps[current_row] = true; } wlv.boguscols = 0; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index f71a47a596..04918e9979 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -215,7 +215,6 @@ void screenclear(void) for (int i = 0; i < default_grid.rows; i++) { grid_clear_line(&default_grid, default_grid.line_offset[i], default_grid.cols, true); - default_grid.line_wraps[i] = false; } ui_call_grid_clear(1); // clear the display @@ -444,6 +443,15 @@ int update_screen(void) display_tick++; // let syntax code know we're in a next round of // display updating + // glyph cache full, very rare + if (schar_cache_clear_if_full()) { + // must use CLEAR, as the contents of screen buffers cannot be + // compared to their previous state here. + // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs) + // we need to revalidate these here as well! + type = MAX(type, UPD_CLEAR); + } + // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. if (msg_did_scroll) { @@ -463,7 +471,7 @@ int update_screen(void) // non-displayed part of msg_grid is considered invalid. for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); + msg_grid.cols, i < p_ch); } } msg_grid.throttled = false; @@ -701,15 +709,15 @@ void end_search_hl(void) screen_search_hl.rm.regprog = NULL; } -static void win_redr_bordertext(win_T *wp, ScreenGrid *grid, VirtText text_chunks, int row, int col) +static void win_redr_bordertext(win_T *wp, VirtText vt, int col) { - for (size_t i = 0; i < text_chunks.size; i++) { - char *text = text_chunks.items[i].text; - int cell = (int)mb_string2cells(text); - int hl_id = text_chunks.items[i].hl_id; - int attr = hl_id ? syn_id2attr(hl_id) : 0; - grid_puts(grid, text, row, col, attr); - col += cell; + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *text = next_virt_text_chunk(vt, &i, &attr); + if (text == NULL) { + break; + } + col += grid_line_puts(col, text, -1, attr); } } @@ -736,67 +744,70 @@ static void win_redr_border(win_T *wp) ScreenGrid *grid = &wp->w_grid_alloc; - schar_T *chars = wp->w_float_config.border_chars; + schar_T chars[8]; + for (int i = 0; i < 8; i++) { + chars[i] = schar_from_str(wp->w_float_config.border_chars[i]); + } int *attrs = wp->w_float_config.border_attr; int *adj = wp->w_border_adj; int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; if (adj[0]) { - grid_puts_line_start(grid, 0); + grid_line_start(grid, 0); if (adj[3]) { - grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + grid_line_put_schar(0, chars[0], attrs[0]); } for (int i = 0; i < icol; i++) { - grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); + grid_line_put_schar(i + adj[3], chars[1], attrs[1]); } if (wp->w_float_config.title) { int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width, wp->w_float_config.title_pos); - win_redr_bordertext(wp, grid, wp->w_float_config.title_chunks, 0, title_col); + win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col); } if (adj[1]) { - grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); + grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); } - grid_puts_line_flush(false); + grid_line_flush(false); } for (int i = 0; i < irow; i++) { if (adj[3]) { - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]); - grid_puts_line_flush(false); + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(0, chars[7], attrs[7]); + grid_line_flush(false); } if (adj[1]) { - int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; - grid_puts_line_start(grid, i + adj[0]); - grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]); - grid_puts_line_flush(false); + int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3; + grid_line_start(grid, i + adj[0]); + grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]); + grid_line_flush(false); } } if (adj[2]) { - grid_puts_line_start(grid, irow + adj[0]); + grid_line_start(grid, irow + adj[0]); if (adj[3]) { - grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]); + grid_line_put_schar(0, chars[6], attrs[6]); } for (int i = 0; i < icol; i++) { - int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; - grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]); + int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5; + grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]); } if (wp->w_float_config.footer) { int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width, wp->w_float_config.footer_pos); - win_redr_bordertext(wp, grid, wp->w_float_config.footer_chunks, grid->rows - 1, footer_col); + win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col); } if (adj[1]) { - grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]); + grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); } - grid_puts_line_flush(false); + grid_line_flush(false); } } @@ -919,7 +930,9 @@ int showmode(void) || (State & MODE_INSERT) || restart_edit != NUL || VIsual_active)); - if (do_mode || reg_recording != 0) { + + bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages)); + if ((do_mode || reg_recording != 0) && can_show_mode) { int sub_attr; if (skip_showmode()) { return 0; // show mode later @@ -1294,23 +1307,25 @@ static void draw_hsep_win(win_T *wp) } /// Get the separator connector for specified window corner of window "wp" -static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner) { // It's impossible for windows to be connected neither vertically nor horizontally // So if they're not vertically connected, assume they're horizontally connected + int c; if (vsep_connected(wp, corner)) { if (hsep_connected(wp, corner)) { - return wp->w_p_fcs_chars.verthoriz; + c = wp->w_p_fcs_chars.verthoriz; } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { - return wp->w_p_fcs_chars.vertright; + c = wp->w_p_fcs_chars.vertright; } else { - return wp->w_p_fcs_chars.vertleft; + c = wp->w_p_fcs_chars.vertleft; } } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { - return wp->w_p_fcs_chars.horizdown; + c = wp->w_p_fcs_chars.horizdown; } else { - return wp->w_p_fcs_chars.horizup; + c = wp->w_p_fcs_chars.horizup; } + return schar_from_char(c); } /// Draw separator connecting characters on the corners of window "wp" @@ -1346,21 +1361,31 @@ static void draw_sep_connectors_win(win_T *wp) win_at_left = frp->fr_parent == NULL; // Draw the appropriate separator connector in every corner where drawing them is necessary - if (!(win_at_top || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), - wp->w_winrow - 1, wp->w_wincol - 1, hl); - } - if (!(win_at_top || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), - wp->w_winrow - 1, W_ENDCOL(wp), hl); - } - if (!(win_at_bottom || win_at_left)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), - W_ENDROW(wp), wp->w_wincol - 1, hl); + // Make sure not to send cursor position updates to ui. + bool top_left = !(win_at_top || win_at_left); + bool top_right = !(win_at_top || win_at_right); + bool bot_left = !(win_at_bottom || win_at_left); + bool bot_right = !(win_at_bottom || win_at_right); + + if (top_left || top_right) { + grid_line_start(&default_grid, wp->w_winrow - 1); + if (top_left) { + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl); + } + if (top_right) { + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl); + } + grid_line_flush(false); } - if (!(win_at_bottom || win_at_right)) { - grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), - W_ENDROW(wp), W_ENDCOL(wp), hl); + if (bot_left || bot_right) { + grid_line_start(&default_grid, W_ENDROW(wp)); + if (bot_left) { + grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl); + } + if (bot_right) { + grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl); + } + grid_line_flush(false); } } @@ -2357,7 +2382,7 @@ static void win_update(win_T *wp, DecorProviders *providers) utf_char2bytes(symbol, &fillbuf[charlen]); // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); + grid_puts(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 216f8a67db..520d3bc2b3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1449,6 +1449,7 @@ void edit_putchar(int c, bool highlight) // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { + // TODO(bfredl): save the schar_T instead grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); pc_status = PC_STATUS_SET; } @@ -1532,7 +1533,7 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, pc_row, pc_col, pc_attr); + grid_puts(&curwin->w_grid, pc_bytes, -1, pc_row, pc_col, pc_attr); } } } @@ -3485,7 +3486,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Otherwise remove the mode message. if (reg_recording != 0 || restart_edit != NUL) { showmode(); - } else if (p_smd && (got_int || !skip_showmode())) { + } else if (p_smd && (got_int || !skip_showmode()) + && !(p_ch == 0 && !ui_has(kUIMessages))) { msg(""); } // Exit Insert mode diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a279b6d051..21c7cdee7d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1271,8 +1271,11 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv) /// it in "*cp". Doesn't give error messages. int eval_foldexpr(win_T *wp, int *cp) { + const sctx_T saved_sctx = current_sctx; const bool use_sandbox = was_set_insecurely(wp, "foldexpr", OPT_LOCAL); + char *arg = wp->w_p_fde; + current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx; emsg_off++; if (use_sandbox) { @@ -1309,6 +1312,7 @@ int eval_foldexpr(win_T *wp, int *cp) } textlock--; clear_evalarg(&EVALARG_EVALUATE, NULL); + current_sctx = saved_sctx; return (int)retval; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 2137dc07e4..858f7c8afd 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5572,7 +5572,7 @@ M.funcs = { jobstart = { args = { 1, 2 }, desc = [=[ - Note: Prefer |vim.system()| in Lua. + Note: Prefer |vim.system()| in Lua (unless using the `pty` option). Spawns {cmd} as a job. If {cmd} is a List it runs directly (no 'shell'). @@ -10973,7 +10973,7 @@ M.funcs = { echo strutf16len('😊') " returns 2 echo strutf16len('ą́') " returns 1 echo strutf16len('ą́', v:true) " returns 3 - + < ]=], name = 'strutf16len', params = { { 'string', 'string' }, { 'countcc', '0|1' } }, diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index e5b1b88eef..5d1da956ee 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -52,8 +52,8 @@ static const char *e_letunexp = N_("E18: Unexpected characters in :let"); static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); -static const char e_setting_str_to_value_with_wrong_type[] - = N_("E963: Setting %s to value with wrong type"); +static const char e_setting_v_str_to_value_with_wrong_type[] + = N_("E963: Setting v:%s to value with wrong type"); static const char e_cannot_use_heredoc_here[] = N_("E991: Cannot use =<< here"); @@ -1389,6 +1389,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t } } +/// Additional handling for setting a v: variable. +/// +/// @return true if the variable should be set normally, +/// false if nothing else needs to be done. +bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv, + const bool copy, const bool watched, bool *const type_error) +{ + if (di->di_tv.v_type == VAR_STRING) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + XFREE_CLEAR(di->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) { + const char *const val = tv_get_string(tv); + // Careful: when assigning to v:errmsg and tv_get_string() + // causes an error message the variable will already be set. + if (di->di_tv.vval.v_string == NULL) { + di->di_tv.vval.v_string = xstrdup(val); + } + } else { + // Take over the string to avoid an extra alloc/free. + di->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type == VAR_NUMBER) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + di->di_tv.vval.v_number = tv_get_number(tv); + if (strcmp(varname, "searchforward") == 0) { + set_search_direction(di->di_tv.vval.v_number ? '/' : '?'); + } else if (strcmp(varname, "hlsearch") == 0) { + no_hlsearch = !di->di_tv.vval.v_number; + redraw_all_later(UPD_SOME_VALID); + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type != tv->v_type) { + *type_error = true; + return false; + } + return true; +} + /// Set variable to the given value /// /// If the variable already exists, the value is updated. Otherwise the variable @@ -1418,31 +1474,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, const bool is_const) FUNC_ATTR_NONNULL_ALL { - dictitem_T *v; - hashtab_T *ht; - dict_T *dict; - const char *varname; - ht = find_var_ht_dict(name, name_len, &varname, &dict); + dict_T *dict; + hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict); const bool watched = tv_dict_is_watched(dict); if (ht == NULL || *varname == NUL) { semsg(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true); + const size_t varname_len = name_len - (size_t)(varname - name); + dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true); // Search in parent scope which is possible to reference from lambda - if (v == NULL) { - v = find_var_in_scoped_ht(name, name_len, true); + if (di == NULL) { + di = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) { return; } typval_T oldtv = TV_INITIAL_VALUE; - if (v != NULL) { + if (di != NULL) { if (is_const) { emsg(_(e_cannot_mod)); return; @@ -1452,9 +1506,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // - Whether the variable is read-only // - Whether the variable value is locked // - Whether the variable is locked - if (var_check_ro(v->di_flags, name, name_len) - || value_check_lock(v->di_tv.v_lock, name, name_len) - || var_check_lock(v->di_flags, name, name_len)) { + if (var_check_ro(di->di_flags, name, name_len) + || value_check_lock(di->di_tv.v_lock, name, name_len) + || var_check_lock(di->di_flags, name, name_len)) { return; } @@ -1462,42 +1516,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Handle setting internal v: variables separately where needed to // prevent changing the type. - if (is_vimvarht(ht)) { - if (v->di_tv.v_type == VAR_STRING) { - XFREE_CLEAR(v->di_tv.vval.v_string); - if (copy || tv->v_type != VAR_STRING) { - const char *const val = tv_get_string(tv); - - // Careful: when assigning to v:errmsg and tv_get_string() - // causes an error message the variable will already be set. - if (v->di_tv.vval.v_string == NULL) { - v->di_tv.vval.v_string = xstrdup(val); - } - } else { - // Take over the string to avoid an extra alloc/free. - v->di_tv.vval.v_string = tv->vval.v_string; - tv->vval.v_string = NULL; - } - return; - } else if (v->di_tv.v_type == VAR_NUMBER) { - v->di_tv.vval.v_number = tv_get_number(tv); - if (strcmp(varname, "searchforward") == 0) { - set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); - } else if (strcmp(varname, "hlsearch") == 0) { - no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(UPD_SOME_VALID); - } - return; - } else if (v->di_tv.v_type != tv->v_type) { - semsg(_(e_setting_str_to_value_with_wrong_type), name); - return; + bool type_error = false; + if (is_vimvarht(ht) + && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) { + if (type_error) { + semsg(_(e_setting_v_str_to_value_with_wrong_type), varname); } + return; } if (watched) { - tv_copy(&v->di_tv, &oldtv); + tv_copy(&di->di_tv, &oldtv); } - tv_clear(&v->di_tv); + tv_clear(&di->di_tv); } else { // Add a new variable. // Can't add "v:" or "a:" variable. if (is_vimvarht(ht) || ht == get_funccal_args_ht()) { @@ -1513,28 +1544,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Make sure dict is valid assert(dict != NULL); - v = xmalloc(offsetof(dictitem_T, di_key) + strlen(varname) + 1); - STRCPY(v->di_key, varname); - if (hash_add(ht, v->di_key) == FAIL) { - xfree(v); + di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1); + memcpy(di->di_key, varname, varname_len + 1); + if (hash_add(ht, di->di_key) == FAIL) { + xfree(di); return; } - v->di_flags = DI_FLAGS_ALLOC; + di->di_flags = DI_FLAGS_ALLOC; if (is_const) { - v->di_flags |= DI_FLAGS_LOCK; + di->di_flags |= DI_FLAGS_LOCK; } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { - tv_copy(tv, &v->di_tv); + tv_copy(tv, &di->di_tv); } else { - v->di_tv = *tv; - v->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv = *tv; + di->di_tv.v_lock = VAR_UNLOCKED; tv_init(tv); } if (watched) { - tv_dict_watcher_notify(dict, v->di_key, &v->di_tv, &oldtv); + tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv); tv_clear(&oldtv); } @@ -1542,7 +1573,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Like :lockvar! name: lock the value and what it contains, but only // if the reference count is up to one. That locks only literal // values. - tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true); + tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true); } } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 95bf4d1c3b..00ba1334b0 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -423,6 +423,7 @@ static void exit_event(void **argv) if (!exiting) { if (ui_client_channel_id) { + ui_client_exit_status = status; os_exit(status); } else { assert(status == 0); // Called from rpc_close(), which passes 0 as status. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4f6b8f2c8f..dc7136196e 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2689,7 +2689,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Obey the 'O' flag in 'cpoptions': overwrite any previous file // message. - if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { + if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) { msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index b37076c62e..86934a645a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -455,6 +455,10 @@ void ex_listdo(exarg_T *eap) tabpage_T *tp; char *save_ei = NULL; + // Temporarily override SHM_OVER and SHM_OVERALL to avoid that file + // message overwrites output from the command. + msg_listdo_overwrite++; + if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) { // Don't do syntax HL autocommands. Skipping the syntax file is a // great speed improvement. @@ -518,9 +522,7 @@ void ex_listdo(exarg_T *eap) if (qf_size == 0 || (size_t)eap->line1 > qf_size) { buf = NULL; } else { - save_clear_shm_value(); ex_cc(eap); - restore_shm_value(); buf = curbuf; i = (int)eap->line1 - 1; @@ -547,9 +549,7 @@ void ex_listdo(exarg_T *eap) if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { // Clear 'shm' to avoid that the file message overwrites // any output from the command. - save_clear_shm_value(); do_argfile(eap, i); - restore_shm_value(); } if (curwin->w_arg_idx != i) { break; @@ -612,11 +612,8 @@ void ex_listdo(exarg_T *eap) break; } - // Go to the next buffer. Clear 'shm' to avoid that the file - // message overwrites any output from the command. - save_clear_shm_value(); + // Go to the next buffer. goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); - restore_shm_value(); // If autocommands took us elsewhere, quit here. if (curbuf->b_fnum != next_fnum) { @@ -633,11 +630,7 @@ void ex_listdo(exarg_T *eap) size_t qf_idx = qf_get_cur_idx(eap); - // Clear 'shm' to avoid that the file message overwrites - // any output from the command. - save_clear_shm_value(); ex_cnext(eap); - restore_shm_value(); // If jumping to the next quickfix entry fails, quit here. if (qf_get_cur_idx(eap) == qf_idx) { @@ -664,6 +657,7 @@ void ex_listdo(exarg_T *eap) listcmd_busy = false; } + msg_listdo_overwrite--; if (save_ei != NULL) { buf_T *bnext; aco_save_T aco; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 44610c81d8..ea93d0fe91 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -906,7 +906,7 @@ void handle_did_throw(void) if (messages != NULL) { do { msglist_T *next = messages->next; - emsg(messages->msg); + emsg_multiline(messages->msg, messages->multiline); xfree(messages->msg); xfree(messages->sfile); xfree(messages); @@ -4591,7 +4591,9 @@ static void ex_cquit(exarg_T *eap) FUNC_ATTR_NORETURN { // this does not always pass on the exit code to the Manx compiler. why? - getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); + int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE; + ui_call_error_exit(status); + getout(status); } /// Do preparations for "qall" and "wqall". diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 1b150ef75d..0704b47d40 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -157,7 +157,7 @@ int aborted_in_try(void) /// When several messages appear in the same command, the first is usually the /// most specific one and used as the exception value. The "severe" flag can be /// set to true, if a later but severer message should be used instead. -bool cause_errthrow(const char *mesg, bool severe, bool *ignore) +bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { msglist_T *elem; @@ -249,6 +249,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) elem = xmalloc(sizeof(msglist_T)); elem->msg = xstrdup(mesg); + elem->multiline = multiline; elem->next = NULL; elem->throw_msg = NULL; *plist = elem; diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 6b3c426722..3ad3368900 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -1,6 +1,8 @@ #ifndef NVIM_EX_EVAL_DEFS_H #define NVIM_EX_EVAL_DEFS_H +#include <stdbool.h> + #include "nvim/pos.h" /// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" @@ -41,11 +43,12 @@ enum { /// message in the list. See cause_errthrow(). typedef struct msglist msglist_T; struct msglist { + msglist_T *next; ///< next of several messages in a row char *msg; ///< original message, allocated char *throw_msg; ///< msg to throw: usually original one char *sfile; ///< value from estack_sfile(), allocated linenr_T slnum; ///< line number for "sfile" - msglist_T *next; ///< next of several messages in a row + bool multiline; ///< whether this is a multiline message }; /// The exception types. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 5d7ddb657a..7ff3e0ec6e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -105,7 +105,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr) // For further ones overwrite the previous one, reset msg_scroll before // calling filemess(). msg_scroll_save = msg_scroll; - if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { + if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) { msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg @@ -316,7 +316,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } - if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { + if (((shortmess(SHM_OVER) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) { msg_scroll = false; // overwrite previous file message } else { msg_scroll = true; // don't overwrite previous file message @@ -459,7 +459,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } if (!silent) { if (dir_of_file_exists(fname)) { - filemess(curbuf, sfname, new_file_message(), 0); + filemess(curbuf, sfname, _("[New]"), 0); } else { filemess(curbuf, sfname, _("[New DIRECTORY]"), 0); } @@ -1718,7 +1718,7 @@ failed: c = true; } if (read_no_eol_lnum) { - msg_add_eol(); + xstrlcat(IObuff, _("[noeol]"), IOSIZE); c = true; } if (ff_error == EOL_DOS) { @@ -2064,11 +2064,6 @@ static void check_marks_read(void) curbuf->b_marks_read = true; } -char *new_file_message(void) -{ - return shortmess(SHM_NEW) ? _("[New]") : _("[New File]"); -} - /// Set the name of the current buffer. Use when the buffer doesn't have a /// name and a ":r" or ":w" command with a file name is used. int set_rw_fname(char *fname, char *sfname) @@ -2142,17 +2137,17 @@ bool msg_add_fileformat(int eol_type) { #ifndef USE_CRNL if (eol_type == EOL_DOS) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"), IOSIZE); + xstrlcat(IObuff, _("[dos]"), IOSIZE); return true; } #endif if (eol_type == EOL_MAC) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"), IOSIZE); + xstrlcat(IObuff, _("[mac]"), IOSIZE); return true; } #ifdef USE_CRNL if (eol_type == EOL_UNIX) { - xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"), IOSIZE); + xstrlcat(IObuff, _("[unix]"), IOSIZE); return true; } #endif @@ -2181,12 +2176,6 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) } } -/// Append message for missing line separator to IObuff. -void msg_add_eol(void) -{ - xstrlcat(IObuff, shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"), IOSIZE); -} - bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST { #if defined(__linux__) || defined(MSWIN) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 1d5ba49301..d874e904d0 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -3344,8 +3344,13 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (kv_size(vt) > 0) { assert(*text == NUL); - for (size_t i = 0; i < kv_size(vt); i++) { - char *new_text = concat_str(text, kv_A(vt, i).text); + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *new_text = next_virt_text_chunk(vt, &i, &attr); + if (new_text == NULL) { + break; + } + new_text = concat_str(text, new_text); xfree(text); text = new_text; } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index fa7f270172..2eeefab27d 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -17,6 +17,7 @@ #include "nvim/arabic.h" #include "nvim/buffer_defs.h" +#include "nvim/drawscreen.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" @@ -36,6 +37,15 @@ // Per-cell attributes static size_t linebuf_size = 0; +// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string. +// Then it instead stores an index into glyph_cache.keys[] which is a flat char array. +// The hash part is used by schar_from_buf() to quickly lookup glyphs which already +// has been interned. schar_get() should used to convert a schar_T value +// back to a string buffer. +// +// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL). +static Set(glyph) glyph_cache = SET_INIT; + /// Determine if dedicated window grid should be used or the default_grid /// /// If UI did not request multigrid support, draw all windows on the @@ -56,25 +66,119 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off) } /// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. -int schar_from_cc(char *p, int c, int u8cc[MAX_MCO]) +schar_T schar_from_cc(int c, int u8cc[MAX_MCO]) { - int len = utf_char2bytes(c, p); + char buf[MAX_SCHAR_SIZE]; + int len = utf_char2bytes(c, buf); for (int i = 0; i < MAX_MCO; i++) { if (u8cc[i] == 0) { break; } - len += utf_char2bytes(u8cc[i], p + len); + len += utf_char2bytes(u8cc[i], buf + len); + } + buf[len] = 0; + return schar_from_buf(buf, (size_t)len); +} + +schar_T schar_from_str(char *str) +{ + if (str == NULL) { + return 0; + } + return schar_from_buf(str, strlen(str)); +} + +/// @param buf need not be NUL terminated, but may not contain embedded NULs. +/// +/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte) +schar_T schar_from_buf(const char *buf, size_t len) +{ + assert(len < MAX_SCHAR_SIZE); + if (len <= 4) { + schar_T sc = 0; + memcpy((char *)&sc, buf, len); + return sc; + } else { + String str = { .data = (char *)buf, .size = len }; + + MHPutStatus status; + uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status); + assert(idx < 0xFFFFFF); +#ifdef ORDER_BIG_ENDIAN + return idx + ((uint32_t)0xFF << 24); +#else + return 0xFF + (idx << 8); +#endif } - p[len] = 0; - return len; } +/// Check if cache is full, and if it is, clear it. +/// +/// This should normally only be called in update_screen() +/// +/// @return true if cache was clered, and all your screen buffers now are hosed +/// and you need to use UPD_CLEAR +bool schar_cache_clear_if_full(void) +{ + // note: critical max is really (1<<24)-1. This gives us some marginal + // until next time update_screen() is called + if (glyph_cache.h.n_keys > (1<<21)) { + set_clear(glyph, &glyph_cache); + return true; + } + return false; +} + +/// For testing. The condition in schar_cache_clear_force is hard to +/// reach, so this function can be used to force a cache clear in a test. +void schar_cache_clear_force(void) +{ + set_clear(glyph, &glyph_cache); + must_redraw = UPD_CLEAR; +} + +bool schar_high(schar_T sc) +{ +#ifdef ORDER_BIG_ENDIAN + return ((sc & 0xFF000000) == 0xFF000000); +#else + return ((sc & 0xFF) == 0xFF); +#endif +} + +void schar_get(char *buf_out, schar_T sc) +{ + if (schar_high(sc)) { +#ifdef ORDER_BIG_ENDIAN + uint32_t idx = sc & (0x00FFFFFF); +#else + uint32_t idx = sc >> 8; +#endif + if (idx >= glyph_cache.h.n_keys) { + abort(); + } + xstrlcpy(buf_out, &glyph_cache.keys[idx], 32); + } else { + memcpy(buf_out, (char *)&sc, 4); + buf_out[4] = NUL; + } +} + +/// @return ascii char or NUL if not ascii +char schar_get_ascii(schar_T sc) +{ +#ifdef ORDER_BIG_ENDIAN + return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL; +#else + return (sc < 0x80) ? (char)sc : NUL; +#endif +} /// clear a line in the grid starting at "off" until "width" characters /// are cleared. void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid) { for (int col = 0; col < width; col++) { - schar_from_ascii(grid->chars[off + (size_t)col], ' '); + grid->chars[off + (size_t)col] = schar_from_ascii(' '); } int fill = valid ? 0 : -1; (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); @@ -93,7 +197,7 @@ bool grid_invalid_row(ScreenGrid *grid, int row) static int line_off2cells(schar_T *line, size_t off, size_t max_off) { - return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1; + return (off + 1 < max_off && line[off + 1] == 0) ? 2 : 1; } /// Return number of display cells for char at grid->chars[off]. @@ -124,7 +228,7 @@ int grid_fix_col(ScreenGrid *grid, int col, int row) col += coloff; if (grid->chars != NULL && col > 0 - && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) { + && grid->chars[grid->line_offset[row] + (size_t)col] == 0) { return col - 1 - coloff; } return col - coloff; @@ -135,8 +239,7 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) { char buf[MB_MAXBYTES + 1]; - buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, buf, row, col, attr); + grid_puts(grid, buf, utf_char2bytes(c, buf), row, col, attr); } /// Get a single character directly from grid.chars into "bytes", which must @@ -155,54 +258,84 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp) if (attrp != NULL) { *attrp = grid->attrs[off]; } - schar_copy(bytes, grid->chars[off]); + schar_get(bytes, grid->chars[off]); } -/// put string '*text' on the window grid at position 'row' and 'col', with -/// attributes 'attr', and update chars[] and attrs[]. -/// Note: only outputs within one row, message is truncated at grid boundary! -/// Note: if grid, row and/or col is invalid, nothing is done. -int grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr) +static bool check_grid(ScreenGrid *grid, int row, int col) { - return grid_puts_len(grid, text, -1, row, col, attr); + grid_adjust(&grid, &row, &col); + // Safety check. The check for negative row and column is to fix issue + // vim/vim#4102. TODO(neovim): find out why row/col could be negative. + if (grid->chars == NULL + || row >= grid->rows || row < 0 + || col >= grid->cols || col < 0) { + return false; + } + return true; } -static ScreenGrid *put_dirty_grid = NULL; -static int put_dirty_row = -1; -static int put_dirty_first = INT_MAX; -static int put_dirty_last = 0; +/// put string 'text' on the window grid at position 'row' and 'col', with +/// attributes 'attr', and update contents of 'grid' +/// @param textlen length of string or -1 to use strlen(text) +/// Note: only outputs within one row! +int grid_puts(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) +{ + if (!check_grid(grid, row, col)) { + if (rdb_flags & RDB_INVALID) { + abort(); + } + return 0; + } + + grid_line_start(grid, row); + int len = grid_line_puts(col, text, textlen, attr); + grid_line_flush(true); + return len; +} -/// Start a group of grid_puts_len calls that builds a single grid line. +static ScreenGrid *grid_line_grid = NULL; +static int grid_line_row = -1; +static int grid_line_coloff = 0; +static int grid_line_first = INT_MAX; +static int grid_line_last = 0; +static bool grid_line_was_invalid = false; + +/// Start a group of grid_line_puts calls that builds a single grid line. /// -/// Must be matched with a grid_puts_line_flush call before moving to +/// Must be matched with a grid_line_flush call before moving to /// another line. -void grid_puts_line_start(ScreenGrid *grid, int row) +void grid_line_start(ScreenGrid *grid, int row) { - int col = 0; // unused + int col = 0; grid_adjust(&grid, &row, &col); - assert(put_dirty_row == -1); - put_dirty_row = row; - put_dirty_grid = grid; + assert(grid_line_row == -1); + grid_line_row = row; + grid_line_grid = grid; + grid_line_coloff = col; + // TODO(bfredl): ugly hackaround, will be fixed in STAGE 2 + grid_line_was_invalid = grid != &default_grid && grid_invalid_row(grid, row); } -void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr) +void grid_line_put_schar(int col, schar_T schar, int attr) { - assert(put_dirty_row == row); - size_t off = grid->line_offset[row] + (size_t)col; - if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) { - schar_copy(grid->chars[off], schar); + assert(grid_line_row >= 0); + ScreenGrid *grid = grid_line_grid; + + size_t off = grid->line_offset[grid_line_row] + (size_t)col; + if (grid->attrs[off] != attr || grid->chars[off] != schar || rdb_flags & RDB_NODELTA) { + grid->chars[off] = schar; grid->attrs[off] = attr; - put_dirty_first = MIN(put_dirty_first, col); + grid_line_first = MIN(grid_line_first, col); // TODO(bfredl): Y U NO DOUBLEWIDTH? - put_dirty_last = MAX(put_dirty_last, col + 1); + grid_line_last = MAX(grid_line_last, col + 1); } grid->vcols[off] = -1; } /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. -int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) +int grid_line_puts(int col, const char *text, int textlen, int attr) { size_t off; const char *ptr = text; @@ -214,37 +347,15 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int int prev_c = 0; // previous Arabic character int pc, nc, nc1; int pcc[MAX_MCO]; - bool do_flush = false; - - grid_adjust(&grid, &row, &col); - // Safety check. The check for negative row and column is to fix issue - // vim/vim#4102. TODO(neovim): find out why row/col could be negative. - if (grid->chars == NULL - || row >= grid->rows || row < 0 - || col >= grid->cols || col < 0) { - return 0; - } + assert(grid_line_row >= 0); + ScreenGrid *grid = grid_line_grid; + int row = grid_line_row; + col += grid_line_coloff; - if (put_dirty_row == -1) { - grid_puts_line_start(grid, row); - do_flush = true; - } else { - if (grid != put_dirty_grid || row != put_dirty_row) { - abort(); - } - } off = grid->line_offset[row] + (size_t)col; int start_col = col; - // When drawing over the right half of a double-wide char clear out the - // left half. Only needed in a terminal. - if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { - // redraw the previous cell, make it empty - put_dirty_first = -1; - put_dirty_last = MAX(put_dirty_last, 1); - } - max_off = grid->line_offset[row] + (size_t)grid->cols; while (col < grid->cols && (len < 0 || (int)(ptr - text) < len) @@ -293,10 +404,12 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int } schar_T buf; - schar_from_cc(buf, u8c, u8cc); + // TODO(bfredl): why not just keep the original byte sequence. arabshape is + // an edge case, treat it as such.. + buf = schar_from_cc(u8c, u8cc); - int need_redraw = schar_cmp(grid->chars[off], buf) - || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) + int need_redraw = grid->chars[off] != buf + || (mbyte_cells == 2 && grid->chars[off + 1] != 0) || grid->attrs[off] != attr || exmode_active || rdb_flags & RDB_NODELTA; @@ -320,20 +433,20 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int // When at the start of the text and overwriting the right half of a // two-cell character in the same grid, truncate that into a '>'. - if (ptr == text && col > 0 && grid->chars[off][0] == 0) { - schar_from_ascii(grid->chars[off - 1], '>'); + if (ptr == text && col > 0 && grid->chars[off] == 0) { + grid->chars[off - 1] = schar_from_ascii('>'); } - schar_copy(grid->chars[off], buf); + grid->chars[off] = buf; grid->attrs[off] = attr; grid->vcols[off] = -1; if (mbyte_cells == 2) { - grid->chars[off + 1][0] = 0; + grid->chars[off + 1] = 0; grid->attrs[off + 1] = attr; grid->vcols[off + 1] = -1; } - put_dirty_first = MIN(put_dirty_first, col); - put_dirty_last = MAX(put_dirty_last, col + mbyte_cells); + grid_line_first = MIN(grid_line_first, col); + grid_line_last = MAX(grid_line_last, col + mbyte_cells); } off += (size_t)mbyte_cells; @@ -346,39 +459,61 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int } } - if (do_flush) { - grid_puts_line_flush(true); - } return col - start_col; } -/// End a group of grid_puts_len calls and send the screen buffer to the UI -/// layer. +void grid_line_fill(int start_col, int end_col, int c, int attr) +{ + ScreenGrid *grid = grid_line_grid; + size_t lineoff = grid->line_offset[grid_line_row]; + start_col += grid_line_coloff; + end_col += grid_line_coloff; + + schar_T sc = schar_from_char(c); + for (int col = start_col; col < end_col; col++) { + size_t off = lineoff + (size_t)col; + if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { + grid->chars[off] = sc; + grid->attrs[off] = attr; + grid_line_first = MIN(grid_line_first, col); + grid_line_last = MAX(grid_line_last, col + 1); + } + grid->vcols[off] = -1; + } +} + +/// End a group of grid_line_puts calls and send the screen buffer to the UI layer. /// /// @param set_cursor Move the visible cursor to the end of the changed region. /// This is a workaround for not yet refactored code paths /// and shouldn't be used in new code. -void grid_puts_line_flush(bool set_cursor) +void grid_line_flush(bool set_cursor) { - assert(put_dirty_row != -1); - if (put_dirty_first < put_dirty_last) { + assert(grid_line_row != -1); + if (grid_line_first < grid_line_last) { + // When drawing over the right half of a double-wide char clear out the + // left half. Only needed in a terminal. + if (grid_line_was_invalid && grid_line_first == 0) { + // redraw the previous cell, make it empty + grid_line_first = -1; + } if (set_cursor) { - ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, - MIN(put_dirty_last, put_dirty_grid->cols - 1)); + ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, + MIN(grid_line_last, grid_line_grid->cols - 1)); } - if (!put_dirty_grid->throttled) { - ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, - put_dirty_last, 0, false); - } else if (put_dirty_grid->dirty_col) { - if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { - put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; + if (!grid_line_grid->throttled) { + ui_line(grid_line_grid, grid_line_row, grid_line_first, grid_line_last, + grid_line_last, 0, false); + } else if (grid_line_grid->dirty_col) { + if (grid_line_last > grid_line_grid->dirty_col[grid_line_row]) { + grid_line_grid->dirty_col[grid_line_row] = grid_line_last; } } - put_dirty_first = INT_MAX; - put_dirty_last = 0; + grid_line_first = INT_MAX; + grid_line_last = 0; } - put_dirty_row = -1; - put_dirty_grid = NULL; + grid_line_row = -1; + grid_line_grid = NULL; } /// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" @@ -415,11 +550,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, " ", 1, row, start_col - 1, 0); + grid_puts(grid, " ", 1, row, start_col - 1, 0); } if (end_col < grid->cols && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, " ", 1, row, end_col, 0); + grid_puts(grid, " ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -429,12 +564,12 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int int dirty_last = 0; int col = start_col; - schar_from_char(sc, c1); + sc = schar_from_char(c1); size_t lineoff = grid->line_offset[row]; for (col = start_col; col < end_col; col++) { size_t off = lineoff + (size_t)col; - if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { - schar_copy(grid->chars[off], sc); + if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { + grid->chars[off] = sc; grid->attrs[off] = attr; if (dirty_first == INT_MAX) { dirty_first = col; @@ -443,15 +578,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int } grid->vcols[off] = -1; if (col == start_col) { - schar_from_char(sc, c2); + sc = schar_from_char(c2); } } if (dirty_last > dirty_first) { - // TODO(bfredl): support a cleared suffix even with a batched line? - if (put_dirty_row == row) { - put_dirty_first = MIN(put_dirty_first, dirty_first); - put_dirty_last = MAX(put_dirty_last, dirty_last); - } else if (grid->throttled) { + if (grid->throttled) { // Note: assumes msg_grid is the only throttled grid assert(grid == &msg_grid); int dirty = 0; @@ -468,10 +599,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int ui_line(grid, row, dirty_first, last, dirty_last, attr, false); } } - - if (end_col == grid->cols) { - grid->line_wraps[row] = false; - } } } @@ -483,11 +610,10 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols) { return (cols > 0 - && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to]) + && ((linebuf_char[off_from] != grid->chars[off_to] || linebuf_attr[off_from] != grid->attrs[off_to] || (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1 - && schar_cmp(linebuf_char[off_from + 1], - grid->chars[off_to + 1]))) + && linebuf_char[off_from + 1] != grid->chars[off_to + 1])) || rdb_flags & RDB_NODELTA)); } @@ -544,7 +670,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (wp->w_p_nu && wp->w_p_rnu) { // do not overwrite the line number, change "123 text" to // "123<<<xt". - while (skip < max_off_from && ascii_isdigit(*linebuf_char[off])) { + while (skip < max_off_from && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) { off++; skip++; } @@ -554,9 +680,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (line_off2cells(linebuf_char, off, max_off_from) > 1) { // When the first half of a double-width character is // overwritten, change the second half to a space. - schar_from_ascii(linebuf_char[off + 1], ' '); + linebuf_char[off + 1] = schar_from_ascii(' '); } - schar_from_ascii(linebuf_char[off], '<'); + linebuf_char[off] = schar_from_ascii('<'); linebuf_attr[off] = HL_ATTR(HLF_AT); off++; } @@ -565,8 +691,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (rlflag) { // Clear rest first, because it's left of the text. if (clear_width > 0) { - while (col <= endcol && grid->chars[off_to][0] == ' ' - && grid->chars[off_to][1] == NUL + while (col <= endcol && grid->chars[off_to] == schar_from_ascii(' ') && grid->attrs[off_to] == bg_attr) { off_to++; col++; @@ -619,9 +744,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle clear_next = true; } - schar_copy(grid->chars[off_to], linebuf_char[off_from]); + grid->chars[off_to] = linebuf_char[off_from]; if (char_cells == 2) { - schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]); + grid->chars[off_to + 1] = linebuf_char[off_from + 1]; } grid->attrs[off_to] = linebuf_attr[off_from]; @@ -645,7 +770,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle if (clear_next) { // Clear the second half of a double-wide character of which the left // half was overwritten with a single-wide character. - schar_from_ascii(grid->chars[off_to], ' '); + grid->chars[off_to] = schar_from_ascii(' '); end_dirty++; } @@ -654,12 +779,10 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle // blank out the rest of the line // TODO(bfredl): we could cache winline widths while (col < clear_width) { - if (grid->chars[off_to][0] != ' ' - || grid->chars[off_to][1] != NUL + if (grid->chars[off_to] != schar_from_ascii(' ') || grid->attrs[off_to] != bg_attr || rdb_flags & RDB_NODELTA) { - grid->chars[off_to][0] = ' '; - grid->chars[off_to][1] = NUL; + grid->chars[off_to] = schar_from_ascii(' '); grid->attrs[off_to] = bg_attr; if (start_dirty == -1) { start_dirty = col; @@ -675,12 +798,6 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle } } - if (clear_width > 0 || wp->w_width != grid->cols) { - // If we cleared after the end of the line, it did not wrap. - // For vsplit, line wrapping is not possible. - grid->line_wraps[row] = false; - } - if (clear_end < end_dirty) { clear_end = end_dirty; } @@ -704,14 +821,12 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) ngrid.vcols = xmalloc(ncells * sizeof(colnr_T)); memset(ngrid.vcols, -1, ncells * sizeof(colnr_T)); ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset)); - ngrid.line_wraps = xmalloc((size_t)rows * sizeof(*ngrid.line_wraps)); ngrid.rows = rows; ngrid.cols = columns; for (new_row = 0; new_row < ngrid.rows; new_row++) { ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols; - ngrid.line_wraps[new_row] = false; grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid); @@ -756,13 +871,11 @@ void grid_free(ScreenGrid *grid) xfree(grid->attrs); xfree(grid->vcols); xfree(grid->line_offset); - xfree(grid->line_wraps); grid->chars = NULL; grid->attrs = NULL; grid->vcols = NULL; grid->line_offset = NULL; - grid->line_wraps = NULL; } /// Doesn't allow reinit, so must only be called by free_all_mem! @@ -885,16 +998,13 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } j += line_count; grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; } else { j = end - 1 - i; temp = (unsigned)grid->line_offset[j]; while ((j -= line_count) >= row) { grid->line_offset[j + line_count] = grid->line_offset[j]; - grid->line_wraps[j + line_count] = grid->line_wraps[j]; } grid->line_offset[j + line_count] = temp; - grid->line_wraps[j + line_count] = false; grid_clear_line(grid, temp, grid->cols, false); } } @@ -933,17 +1043,14 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } j -= line_count; grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); - grid->line_wraps[j] = false; } else { // whole width, moving the line pointers is faster j = row + i; temp = (unsigned)grid->line_offset[j]; while ((j += line_count) <= end - 1) { grid->line_offset[j - line_count] = grid->line_offset[j]; - grid->line_wraps[j - line_count] = grid->line_wraps[j]; } grid->line_offset[j - line_count] = temp; - grid->line_wraps[j - line_count] = false; grid_clear_line(grid, temp, grid->cols, false); } } diff --git a/src/nvim/grid.h b/src/nvim/grid.h index 0db97345d1..9cdc6c6677 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -33,30 +33,25 @@ EXTERN colnr_T *linebuf_vcol INIT(= NULL); // screen grid. /// Put a ASCII character in a screen cell. -static inline void schar_from_ascii(char *p, const char c) -{ - p[0] = c; - p[1] = 0; -} +/// +/// If `x` is a compile time constant, schar_from_ascii(x) will also be. +/// But the specific value varies per plattform. +#ifdef ORDER_BIG_ENDIAN +# define schar_from_ascii(x) ((schar_T)((x) << 24)) +#else +# define schar_from_ascii(x) ((schar_T)(x)) +#endif /// Put a unicode character in a screen cell. -static inline int schar_from_char(char *p, int c) -{ - int len = utf_char2bytes(c, p); - p[len] = NUL; - return len; -} - -/// compare the contents of two screen cells. -static inline int schar_cmp(char *sc1, char *sc2) -{ - return strncmp(sc1, sc2, sizeof(schar_T)); -} - -/// copy the contents of screen cell `sc2` into cell `sc1` -static inline void schar_copy(char *sc1, char *sc2) +static inline schar_T schar_from_char(int c) { - xstrlcpy(sc1, sc2, sizeof(schar_T)); + schar_T sc = 0; + if (c >= 0x200000) { + // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences + c = 0xFFFD; + } + utf_char2bytes(c, (char *)&sc); + return sc; } #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 4ad7d4cdb4..100d648263 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -9,9 +9,13 @@ #include "nvim/types.h" #define MAX_MCO 6 // fixed value for 'maxcombine' +// Includes final NUL. at least 4*(MAX_MCO+1)+1 +#define MAX_SCHAR_SIZE 32 -// The characters and attributes drawn on grids. -typedef char schar_T[(MAX_MCO + 1) * 4 + 1]; +// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianess) +// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4) + +typedef uint32_t schar_T; typedef int sattr_T; enum { @@ -40,15 +44,14 @@ enum { /// attrs[] contains the highlighting attribute for each cell. /// /// vcols[] contains the virtual columns in the line. -1 means not available -/// (below last line), MAXCOL means after the end of the line. +/// or before buffer text, MAXCOL means after the end of the line. +/// -2 or -3 means in fold column and a mouse click should: +/// -2: open a fold +/// -3: close a fold /// /// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start /// of line 'n'. These offsets are in general not linear, as full screen scrolling /// is implemented by rotating the offsets in the line_offset array. -/// -/// line_wraps[] is an array of boolean flags indicating if the screen line -/// wraps to the next line. It can only be true if a window occupies the entire -/// screen width. typedef struct ScreenGrid ScreenGrid; struct ScreenGrid { handle_T handle; @@ -57,7 +60,6 @@ struct ScreenGrid { sattr_T *attrs; colnr_T *vcols; size_t *line_offset; - char *line_wraps; // last column that was drawn (not cleared with the default background). // only used when "throttled" is set. Not allocated by grid_alloc! @@ -113,7 +115,7 @@ struct ScreenGrid { bool comp_disabled; }; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ false, 0, 0, NULL, false, true, 0, \ 0, 0, 0, 0, 0, false } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index df4bffdac3..3728db31d8 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -86,7 +86,7 @@ static int get_attr_entry(HlEntry entry) } retry: {} - MhPutStatus status; + MHPutStatus status; uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status); if (status == kMHExisting) { return (int)k; diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 4f9677650f..f175a7abb0 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -24,6 +24,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_eval.h" #include "nvim/fold.h" #include "nvim/globals.h" @@ -359,6 +360,9 @@ int nlua_setvar(lua_State *lstate) Error err = ERROR_INIT; dictitem_T *di = dict_check_writable(dict, key, del, &err); if (ERROR_SET(&err)) { + nlua_push_errstr(lstate, "%s", err.msg); + api_clear_error(&err); + lua_error(lstate); return 0; } @@ -394,6 +398,15 @@ int nlua_setvar(lua_State *lstate) di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { + bool type_error = false; + if (dict == &vimvardict + && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { + tv_clear(&tv); + if (type_error) { + return luaL_error(lstate, "Setting v:%s to value with wrong type", key.data); + } + return 0; + } if (watched) { tv_copy(&di->di_tv, &oldtv); } diff --git a/src/nvim/main.c b/src/nvim/main.c index d9ca82784f..4b3244de12 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -341,7 +341,7 @@ int main(int argc, char **argv) uint64_t rv = ui_client_start_server(params.argc, params.argv); if (!rv) { os_errmsg("Failed to start Nvim server!\n"); - getout(1); + os_exit(1); } ui_client_channel_id = rv; } @@ -659,6 +659,9 @@ void os_exit(int r) if (ui_client_channel_id) { ui_client_stop(); + if (r == 0) { + r = ui_client_exit_status; + } } else { ui_flush(); ui_call_stop(); @@ -1579,6 +1582,7 @@ static void handle_tag(char *tagname) // If the user doesn't want to edit the file then we quit here. if (swap_exists_did_quit) { + ui_call_error_exit(1); getout(1); } } @@ -1722,6 +1726,7 @@ static void create_windows(mparm_T *parmp) if (got_int || only_one_window()) { // abort selected or quit and only one window did_emsg = false; // avoid hit-enter prompt + ui_call_error_exit(1); getout(1); } // We can't close the window, it would disturb what @@ -1825,6 +1830,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd) if (got_int || only_one_window()) { // abort selected and only one window did_emsg = false; // avoid hit-enter prompt + ui_call_error_exit(1); getout(1); } win_close(curwin, true, false); @@ -2235,6 +2241,7 @@ static void usage(void) static void check_swap_exists_action(void) { if (swap_exists_action == SEA_QUIT) { + ui_call_error_exit(1); getout(1); } handle_swap_exists(NULL); diff --git a/src/nvim/map.c b/src/nvim/map.c index e1d0646083..54f1969df8 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -47,25 +47,6 @@ static inline uint32_t hash_cstr_t(const char *s) #define equal_cstr_t strequal -// when used as a key, String doesn't need to be NUL terminated, -// and can also contain embedded NUL:s as part of the data. -static inline uint32_t hash_String(String s) -{ - uint32_t h = 0; - for (size_t i = 0; i < s.size; i++) { - h = (h << 5) - h + (uint8_t)s.data[i]; - } - return h; -} - -static inline bool equal_String(String a, String b) -{ - if (a.size != b.size) { - return false; - } - return memcmp(a.data, b.data, a.size) == 0; -} - static inline uint32_t hash_HlEntry(HlEntry ae) { const uint8_t *data = (const uint8_t *)&ae; diff --git a/src/nvim/map.h b/src/nvim/map.h index 23a5ea36a3..2d5517c552 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -18,6 +18,25 @@ typedef const char *cstr_t; typedef void *ptr_t; +// when used as a key, String doesn't need to be NUL terminated, +// and can also contain embedded NUL:s as part of the data. +static inline uint32_t hash_String(String s) +{ + uint32_t h = 0; + for (size_t i = 0; i < s.size; i++) { + h = (h << 5) - h + (uint8_t)s.data[i]; + } + return h; +} + +static inline bool equal_String(String a, String b) +{ + if (a.size != b.size) { + return false; + } + return memcmp(a.data, b.data, a.size) == 0; +} + #define Set(type) Set_##type #define Map(T, U) Map_##T##U #define PMap(T) Map(T, ptr_t) @@ -57,7 +76,7 @@ typedef enum { kMHExisting = 0, kMHNewKeyDidFit, kMHNewKeyRealloc, -} MhPutStatus; +} MHPutStatus; void mh_clear(MapHash *h); void mh_realloc(MapHash *h, uint32_t n_min_buckets); @@ -65,20 +84,22 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets); // layer 1: key type specific defs // This is all need for sets. -#define KEY_DECLS(T) \ +#define MH_DECLS(T, K, K_query) \ typedef struct { \ MapHash h; \ - T *keys; \ + K *keys; \ } Set(T); \ \ - uint32_t mh_find_bucket_##T(Set(T) *set, T key, bool put); \ - uint32_t mh_get_##T(Set(T) *set, T key); \ + uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \ + uint32_t mh_get_##T(Set(T) *set, K_query key); \ void mh_rehash_##T(Set(T) *set); \ - uint32_t mh_put_##T(Set(T) *set, T key, MhPutStatus *new); \ + uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \ + +#define KEY_DECLS(T) \ + MH_DECLS(T, T, T) \ uint32_t mh_delete_##T(Set(T) *set, T *key); \ - \ static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \ - MhPutStatus status; \ + MHPutStatus status; \ uint32_t k = mh_put_##T(set, key, &status); \ if (key_alloc) { \ *key_alloc = &set->keys[k]; \ @@ -120,6 +141,7 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets); #define quasiquote(x, y) x##y +MH_DECLS(glyph, char, String) KEY_DECLS(int) KEY_DECLS(cstr_t) KEY_DECLS(ptr_t) diff --git a/src/nvim/map_glyph_cache.c b/src/nvim/map_glyph_cache.c new file mode 100644 index 0000000000..6dcbfe0532 --- /dev/null +++ b/src/nvim/map_glyph_cache.c @@ -0,0 +1,102 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Specialized version of Set() where interned strings is stored in a compact, +// NUL-separated char array. +// `String key` lookup keys don't need to be NULL terminated, but they +// must not contain embedded NUL:s. When reading a key from set->keys, they +// are always NUL terminated, though. Thus, it is enough to store an index into +// this array, and use strlen(), to retrive an interned key. + +#include "nvim/api/private/helpers.h" +#include "nvim/map.h" + +uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put) +{ + MapHash *h = &set->h; + uint32_t step = 0; + uint32_t mask = h->n_buckets - 1; + uint32_t k = hash_String(key); + uint32_t i = k & mask; + uint32_t last = i; + uint32_t site = put ? last : MH_TOMBSTONE; + while (!mh_is_empty(h, i)) { + if (mh_is_del(h, i)) { + if (site == last) { + site = i; + } + } else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) { + return i; + } + i = (i + (++step)) & mask; + if (i == last) { + abort(); + } + } + if (site == last) { + site = i; + } + return site; +} + +/// @return index into set->keys if found, MH_TOMBSTONE otherwise +uint32_t mh_get_glyph(Set(glyph) *set, String key) +{ + if (set->h.n_buckets == 0) { + return MH_TOMBSTONE; + } + uint32_t idx = mh_find_bucket_glyph(set, key, false); + return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE; +} + +void mh_rehash_glyph(Set(glyph) *set) +{ + // assume the format of set->keys, i e NUL terminated strings + for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) { + uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true); + // there must be tombstones when we do a rehash + if (!mh_is_empty((&set->h), idx)) { + abort(); + } + set->h.hash[idx] = k + 1; + } + set->h.n_occupied = set->h.size = set->h.n_keys; +} + +uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new) +{ + MapHash *h = &set->h; + // Might rehash ahead of time if "key" already existed. But it was + // going to happen soon anyway. + if (h->n_occupied >= h->upper_bound) { + mh_realloc(h, h->n_buckets + 1); + mh_rehash_glyph(set); + } + + uint32_t idx = mh_find_bucket_glyph(set, key, true); + + if (mh_is_either(h, idx)) { + h->size++; + h->n_occupied++; + + uint32_t size = (uint32_t)key.size + 1; // NUL takes space + uint32_t pos = h->n_keys; + h->n_keys += size; + if (h->n_keys > h->keys_capacity) { + h->keys_capacity = MAX(h->keys_capacity * 2, 64); + set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char)); + *new = kMHNewKeyRealloc; + } else { + *new = kMHNewKeyDidFit; + } + memcpy(&set->keys[pos], key.data, key.size); + set->keys[pos + key.size] = NUL; + h->hash[idx] = pos + 1; + return pos; + } else { + *new = kMHExisting; + uint32_t pos = h->hash[idx] - 1; + assert(equal_String(cstr_as_string(&set->keys[pos]), key)); + return pos; + } +} diff --git a/src/nvim/map_key_impl.c.h b/src/nvim/map_key_impl.c.h index 7e7b2f74fe..4d060f5fb8 100644 --- a/src/nvim/map_key_impl.c.h +++ b/src/nvim/map_key_impl.c.h @@ -80,7 +80,7 @@ void KEY_NAME(mh_rehash_)(SET_TYPE *set) /// if new item, indicates if keys[] was resized. /// /// @return keys index -uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MhPutStatus *new) +uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new) { MapHash *h = &set->h; // Might rehash ahead of time if "key" already existed. But it was diff --git a/src/nvim/map_value_impl.c.h b/src/nvim/map_value_impl.c.h index fffda280f0..8f07bd8fa7 100644 --- a/src/nvim/map_value_impl.c.h +++ b/src/nvim/map_value_impl.c.h @@ -28,7 +28,7 @@ VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc, bool *new_item) { - MhPutStatus status; + MHPutStatus status; uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status); if (status != kMHExisting) { if (status == kMHNewKeyRealloc) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 81760dd017..dba4dba600 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -640,7 +640,7 @@ int emsg_not_now(void) return false; } -static bool emsg_multiline(const char *s, bool multiline) +bool emsg_multiline(const char *s, bool multiline) { int attr; bool ignore = false; @@ -663,7 +663,7 @@ static bool emsg_multiline(const char *s, bool multiline) // be found, the message will be displayed later on.) "ignore" is set // when the message should be ignored completely (used for the // interrupt message). - if (cause_errthrow(s, severe, &ignore)) { + if (cause_errthrow(s, multiline, severe, &ignore)) { if (!ignore) { did_emsg++; } @@ -1907,10 +1907,13 @@ void msg_prt_line(const char *s, int list) continue; } else { attr = 0; - c = (unsigned char)(*s++); - in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' '); - if (!in_multispace) { - multispace_pos = 0; + c = (uint8_t)(*s++); + if (list) { + in_multispace = c == ' ' && (*s == ' ' + || (col > 0 && s[-2] == ' ')); + if (!in_multispace) { + multispace_pos = 0; + } } if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) { // tab amount depends on current column @@ -1950,7 +1953,7 @@ void msg_prt_line(const char *s, int list) // the same in plain text. attr = HL_ATTR(HLF_0); } else if (c == ' ') { - if (list && lead != NULL && s <= lead && in_multispace + if (lead != NULL && s <= lead && in_multispace && curwin->w_p_lcs_chars.leadmultispace != NULL) { c = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++]; if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { @@ -1963,7 +1966,7 @@ void msg_prt_line(const char *s, int list) } else if (trail != NULL && s > trail) { c = curwin->w_p_lcs_chars.trail; attr = HL_ATTR(HLF_0); - } else if (list && in_multispace + } else if (in_multispace && curwin->w_p_lcs_chars.multispace != NULL) { c = curwin->w_p_lcs_chars.multispace[multispace_pos++]; if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) { @@ -2004,7 +2007,7 @@ static const char *screen_puts_mbyte(const char *s, int l, int attr) return s; } - grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr); + grid_puts(&msg_grid_adj, s, l, msg_row, msg_col, attr); if (cmdmsg_rl) { msg_col -= cw; if (msg_col == 0) { @@ -2705,7 +2708,7 @@ static void t_puts(int *t_col, const char *t_s, const char *s, int attr) attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&msg_grid_adj, t_s, (int)(s - t_s), msg_row, msg_col, attr); + grid_puts(&msg_grid_adj, t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; // If the string starts with a composing character don't increment the @@ -3091,9 +3094,9 @@ void msg_moremsg(int full) char *s = _("-- More --"); attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); - grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); + grid_puts(&msg_grid_adj, s, -1, Rows - 1, 0, attr); if (full) { - grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), -1, Rows - 1, vim_strsize(s), attr); } } diff --git a/src/nvim/message.h b/src/nvim/message.h index 191d3b8da7..78cd5b7d0e 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -68,6 +68,8 @@ EXTERN int msg_scrolled_at_flush INIT(= 0); EXTERN int msg_grid_scroll_discount INIT(= 0); +EXTERN int msg_listdo_overwrite INIT(= 0); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b8c80cadf5..9b09a3fdf3 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1849,74 +1849,66 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) int click_grid = mouse_grid; int click_row = mouse_row; int click_col = mouse_col; - int mouse_char = ' '; - int max_row = Rows; - int max_col = Columns; - bool multigrid = ui_has(kUIMultigrid); - colnr_T col_from_screen = -1; + // XXX: this doesn't change click_grid if it is 1, even with multigrid win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp && multigrid) { - max_row = wp->w_grid_alloc.rows; - max_col = wp->w_grid_alloc.cols; - } - - if (wp && mouse_row >= 0 && mouse_row < max_row - && mouse_col >= 0 && mouse_col < max_col) { - ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; - int fdc = win_fdccol_count(wp); - int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row; - int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col; - - if (gp->chars != NULL) { - const size_t off = gp->line_offset[use_row] + (size_t)use_col; - - // Only use vcols[] after the window was redrawn. Mainly matters - // for tests, a user would not click before redrawing. - if (wp->w_redr_type == 0) { - col_from_screen = gp->vcols[off]; - } - - if (col_from_screen == MAXCOL) { - // When clicking after end of line, still need to set correct curswant - size_t off_l = gp->line_offset[use_row]; - if (gp->vcols[off_l] < MAXCOL) { - // Binary search to find last char in line - size_t off_r = off; - while (off_l < off_r) { - size_t off_m = (off_l + off_r + 1) / 2; - if (gp->vcols[off_m] < MAXCOL) { - off_l = off_m; - } else { - off_r = off_m - 1; - } - } - *vcolp = gp->vcols[off_r] + (int)(off - off_r); + // Only use vcols[] after the window was redrawn. Mainly matters + // for tests, a user would not click before redrawing. + if (wp == NULL || wp->w_redr_type != 0) { + return; + } + ScreenGrid *gp = &wp->w_grid; + int start_row = 0; + int start_col = 0; + grid_adjust(&gp, &start_row, &start_col); + if (gp->handle != click_grid || gp->chars == NULL) { + return; + } + click_row += start_row; + click_col += start_col; + if (click_row < 0 || click_row >= gp->rows + || click_col < 0 || click_col >= gp->cols) { + return; + } + + const size_t off = gp->line_offset[click_row] + (size_t)click_col; + colnr_T col_from_screen = gp->vcols[off]; + + if (col_from_screen == MAXCOL) { + // When clicking after end of line, still need to set correct curswant + size_t off_l = gp->line_offset[click_row] + (size_t)start_col; + if (gp->vcols[off_l] < MAXCOL) { + // Binary search to find last char in line + size_t off_r = off; + while (off_l < off_r) { + size_t off_m = (off_l + off_r + 1) / 2; + if (gp->vcols[off_m] < MAXCOL) { + off_l = off_m; } else { - // Shouldn't normally happen - *vcolp = MAXCOL; + off_r = off_m - 1; } - } else if (col_from_screen >= 0) { - // Use the virtual column from vcols[], it is accurate also after - // concealed characters. - *vcolp = col_from_screen; } - - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - mouse_char = utf_ptr2char((char *)gp->chars[off]); - } - - // Check for position outside of the fold column. - if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : - click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { - mouse_char = ' '; + colnr_T eol_vcol = gp->vcols[off_r]; + assert(eol_vcol < MAXCOL); + if (eol_vcol < 0) { + // Empty line or whole line before w_leftcol, + // with columns before buffer text + eol_vcol = wp->w_leftcol - 1; + } + *vcolp = eol_vcol + (int)(off - off_r); + } else { + // Empty line or whole line before w_leftcol + *vcolp = click_col - start_col + wp->w_leftcol; } + } else if (col_from_screen >= 0) { + // Use the virtual column from vcols[], it is accurate also after + // concealed characters. + *vcolp = col_from_screen; } - if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { + if (col_from_screen == -2) { *flagsp |= MOUSE_FOLD_OPEN; - } else if (mouse_char != ' ') { + } else if (col_from_screen == -3) { *flagsp |= MOUSE_FOLD_CLOSE; } } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c3b1022db2..37e32729cc 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -9,6 +9,7 @@ #include "mpack/conv.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/grid.h" #include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel_defs.h" @@ -497,13 +498,13 @@ redo: if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) { g->clear_width = repeat; } else { + schar_T sc = schar_from_buf(cellbuf, cellsize); for (int r = 0; r < repeat; r++) { if (g->coloff >= (int)grid_line_buf_size) { p->state = -1; return false; } - memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize); - grid_line_buf_char[g->coloff][cellsize] = NUL; + grid_line_buf_char[g->coloff] = sc; grid_line_buf_attr[g->coloff++] = g->cur_attr; } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4280d62a95..309b6e2568 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2078,18 +2078,16 @@ static void display_showcmd(void) msg_grid_validate(); int showcmd_row = Rows - 1; - grid_puts_line_start(&msg_grid_adj, showcmd_row); + grid_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, - HL_ATTR(HLF_MSG)); + grid_line_puts(sc_col, showcmd_buf, -1, HL_ATTR(HLF_MSG)); } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row, - sc_col + len, HL_ATTR(HLF_MSG)); + grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG)); - grid_puts_line_flush(false); + grid_line_flush(false); } /// When "check" is false, prepare for commands that scroll the window. @@ -4513,7 +4511,7 @@ static void nv_replace(cmdarg_T *cap) // Visual mode "r" if (VIsual_active) { if (got_int) { - reset_VIsual(); + got_int = false; } if (had_ctrl_v) { // Use a special (negative) number to make a difference between a diff --git a/src/nvim/option.c b/src/nvim/option.c index ae4e7c1fda..c34999ed32 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1915,6 +1915,9 @@ bool parse_winhl_opt(win_T *wp) char *commap = xstrchrnul(hi, ','); size_t len = (size_t)(commap - hi); int hl_id = len ? syn_check_group(hi, len) : -1; + if (hl_id == 0) { + return false; + } int hl_id_link = nlen ? syn_check_group(p, nlen) : 0; HlAttrs attrs = HLATTRS_INIT; @@ -1967,6 +1970,10 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) curbuf->b_p_script_ctx[indir & PV_MASK] = last_set; } else if (indir & PV_WIN) { curwin->w_p_script_ctx[indir & PV_MASK] = last_set; + if (both) { + // also setting the "all buffers" value + curwin->w_allbuf_opt.wo_script_ctx[indir & PV_MASK] = last_set; + } } } } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 14f29682e1..317bc989e5 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -240,11 +240,7 @@ typedef enum { enum { SHM_RO = 'r', ///< Readonly. SHM_MOD = 'm', ///< Modified. - SHM_FILE = 'f', ///< (file 1 of 2) - SHM_LAST = 'i', ///< Last line incomplete. - SHM_TEXT = 'x', ///< tx instead of textmode. SHM_LINES = 'l', ///< "L" instead of "lines". - SHM_NEW = 'n', ///< "[New]" instead of "[New file]". SHM_WRI = 'w', ///< "[w]" instead of "written". SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS. SHM_WRITE = 'W', ///< Don't use "written" at all. @@ -260,11 +256,10 @@ enum { SHM_RECORDING = 'q', ///< Short recording message. SHM_FILEINFO = 'F', ///< No file info messages. SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]' - SHM_LEN = 30, ///< Max length of all flags together plus a NUL character. }; /// Represented by 'a' flag. #define SHM_ALL_ABBREVIATIONS ((char[]) { \ - SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ + SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, \ 0 }) // characters for p_go: diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 3221e5b6e9..429a70eb38 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2790,8 +2790,8 @@ return { and the value of that item: item default Used for ~ - stl ' ' or '^' statusline of the current window - stlnc ' ' or '=' statusline of the non-current windows + stl ' ' statusline of the current window + stlnc ' ' statusline of the non-current windows wbr ' ' window bar horiz '─' or '-' horizontal separators |:split| horizup '┴' or '-' upwards facing horizontal separator @@ -2810,9 +2810,7 @@ return { eob '~' empty lines at the end of a buffer lastline '@' 'display' contains lastline/truncate - Any one that is omitted will fall back to the default. For "stl" and - "stlnc" the space will be used when there is highlighting, '^' or '=' - otherwise. + Any one that is omitted will fall back to the default. Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and "verthoriz" are only used when 'laststatus' is 3, since only vertical @@ -3938,9 +3936,8 @@ return { cb = 'did_set_ignorecase', defaults = { if_true = false }, desc = [=[ - Ignore case in search patterns. Also used when searching in the tags - file. - Also see 'smartcase' and 'tagcase'. + Ignore case in search patterns, completion, and when searching the tags file. + See also 'smartcase' and 'tagcase'. Can be overruled by using "\c" or "\C" in the pattern, see |/ignorecase|. ]=], @@ -4444,12 +4441,11 @@ return { deny_duplicates = true, desc = [=[ List of words that change the behavior of the |jumplist|. - stack Make the jumplist behave like the tagstack or like a - web browser. Relative location of entries in the - jumplist is preserved at the cost of discarding - subsequent entries when navigating backwards in the - jumplist and then jumping to a location. - |jumplist-stack| + stack Make the jumplist behave like the tagstack. + Relative location of entries in the jumplist is + preserved at the cost of discarding subsequent entries + when navigating backwards in the jumplist and then + jumping to a location. |jumplist-stack| view When moving through the jumplist, |changelist|, |alternate-file| or using |mark-motions| try to @@ -7243,23 +7239,17 @@ return { { abbreviation = 'shm', cb = 'did_set_shortmess', - defaults = { if_true = 'filnxtToOCF' }, + defaults = { if_true = 'ltToOCF' }, desc = [=[ This option helps to avoid all the |hit-enter| prompts caused by file messages, for example with CTRL-G, and to avoid some other messages. It is a list of flags: flag meaning when present ~ - f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f* - i use "[noeol]" instead of "[Incomplete last line]" *shm-i* l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* m use "[+]" instead of "[Modified]" *shm-m* - n use "[New]" instead of "[New File]" *shm-n* r use "[RO]" instead of "[readonly]" *shm-r* w use "[w]" instead of "written" for file write message *shm-w* and "[a]" instead of "appended" for ':w >> file' command - x use "[dos]" instead of "[dos format]", "[unix]" *shm-x* - instead of "[unix format]" and "[mac]" instead of "[mac - format]" a all of the above abbreviations *shm-a* o overwrite message for writing a file with subsequent *shm-o* diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 17afd10ee3..797c3cb554 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -69,8 +69,6 @@ static const char e_backupext_and_patchmode_are_equal[] = N_("E589: 'backupext' and 'patchmode' are equal"); static const char e_showbreak_contains_unprintable_or_wide_character[] = N_("E595: 'showbreak' contains unprintable or wide character"); -static const char e_internal_error_shortmess_too_long[] - = N_("E1336: Internal error: shortmess too long"); static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -134,11 +132,12 @@ static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelt static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; /// All possible flags for 'shm'. -static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, +/// the literal chars before 0 are removed flags. these are safely ignored +static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO, - SHM_SEARCHCOUNT, 0, }; + SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, }; /// After setting various option values: recompute variables that depend on /// option values. @@ -151,13 +150,13 @@ void didset_string_options(void) (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); - (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); } @@ -2269,36 +2268,6 @@ int check_ff_value(char *p) return check_opt_strings(p, p_ff_values, false); } -static char shm_buf[SHM_LEN]; -static int set_shm_recursive = 0; - -/// Save the actual shortmess Flags and clear them temporarily to avoid that -/// file messages overwrites any output from the following commands. -/// -/// Caller must make sure to first call save_clear_shm_value() and then -/// restore_shm_value() exactly the same number of times. -void save_clear_shm_value(void) -{ - if (strlen(p_shm) >= SHM_LEN) { - iemsg(e_internal_error_shortmess_too_long); - return; - } - - if (++set_shm_recursive == 1) { - STRCPY(shm_buf, p_shm); - set_option_value_give_err("shm", STATIC_CSTR_AS_OPTVAL(""), 0); - } -} - -/// Restore the shortmess Flags set from the save_clear_shm_value() function. -void restore_shm_value(void) -{ - if (--set_shm_recursive == 0) { - set_option_value_give_err("shm", CSTR_AS_OPTVAL(shm_buf), 0); - memset(shm_buf, 0, SHM_LEN); - } -} - static const char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'"); static const char e_conflicts_with_value_of_fillchars[] diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index abeb020645..763d30d4a2 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -256,9 +256,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) QUEUE_FOREACH(q, &args_q, { ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); + QUEUE_REMOVE(q); xfree(arg_node->arg); xfree(arg_node); - QUEUE_REMOVE(q); if (!QUEUE_EMPTY(&args_q)) { xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); } diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index fe31c70d5d..488c893bd8 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -507,14 +507,14 @@ void pum_redraw(void) const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm; int attr = attrs[0]; // start with "word" highlight - grid_puts_line_start(&pum_grid, row); + grid_line_start(&pum_grid, row); // prepend a space if there is room if (extra_space) { if (pum_rl) { - grid_putchar(&pum_grid, ' ', row, col_off + 1, attr); + grid_line_puts(col_off + 1, " ", 1, attr); } else { - grid_putchar(&pum_grid, ' ', row, col_off - 1, attr); + grid_line_puts(col_off - 1, " ", 1, attr); } } @@ -580,13 +580,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&pum_grid, rt, (int)strlen(rt), row, grid_col - size + 1, attr); + grid_line_puts(grid_col - size + 1, rt, -1, attr); xfree(rt_start); xfree(st); grid_col -= width; } else { - // use grid_puts_len() to truncate the text - grid_puts(&pum_grid, st, row, grid_col, attr); + // use grid_line_puts() to truncate the text + grid_line_puts(grid_col, st, -1, attr); xfree(st); grid_col += width; } @@ -597,11 +597,10 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1, - attr); + grid_line_puts(grid_col - 1, " ", 2, attr); grid_col -= 2; } else { - grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr); + grid_line_puts(grid_col, " ", 2, attr); grid_col += 2; } totwidth += 2; @@ -632,37 +631,31 @@ void pum_redraw(void) } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, - grid_col + 1, ' ', ' ', attr); + grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, ' ', attr); grid_col = col_off - pum_base_width - n + 1; } else { - grid_fill(&pum_grid, row, row + 1, grid_col, - col_off + pum_base_width + n, ' ', ' ', attr); + grid_line_fill(grid_col, col_off + pum_base_width + n, ' ', attr); grid_col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1, - ' ', ' ', attr); + grid_line_fill(col_off - pum_width + 1, grid_col + 1, ' ', attr); } else { - grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ', - attr); + grid_line_fill(grid_col, col_off + pum_width, ' ', attr); } if (pum_scrollbar > 0) { if (pum_rl) { - grid_putchar(&pum_grid, ' ', row, col_off - pum_width, - i >= thumb_pos && i < thumb_pos + thumb_height - ? attr_thumb : attr_scroll); + grid_line_puts(col_off - pum_width, " ", 1, + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } else { - grid_putchar(&pum_grid, ' ', row, col_off + pum_width, - i >= thumb_pos && i < thumb_pos + thumb_height - ? attr_thumb : attr_scroll); + grid_line_puts(col_off + pum_width, " ", 1, + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } } - grid_puts_line_flush(false); + grid_line_flush(false); row++; } } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 1d0a987780..14c7e56e97 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -59,11 +59,7 @@ #define un_Magic(x) ((x) + 256) #define is_Magic(x) ((x) < 0) -// We should define ftpr as a pointer to a function returning a pointer to -// a function returning a pointer to a function ... -// This is impossible, so we declare a pointer to a function returning a -// pointer to a function returning void. This should work for all compilers. -typedef void (*(*fptr_T)(int *, int))(void); +typedef void (*fptr_T)(int *, int); static int no_Magic(int x) { @@ -1494,34 +1490,14 @@ static inline char *cstrchr(const char *const s, const int c) // regsub stuff // //////////////////////////////////////////////////////////////// -// This stuff below really confuses cc on an SGI -- webb - -static fptr_T do_upper(int *d, int c) +static void do_upper(int *d, int c) { *d = mb_toupper(c); - - return (fptr_T)NULL; -} - -static fptr_T do_Upper(int *d, int c) -{ - *d = mb_toupper(c); - - return (fptr_T)do_Upper; } -static fptr_T do_lower(int *d, int c) +static void do_lower(int *d, int c) { *d = mb_tolower(c); - - return (fptr_T)NULL; -} - -static fptr_T do_Lower(int *d, int c) -{ - *d = mb_tolower(c); - - return (fptr_T)do_Lower; } /// regtilde(): Replace tildes in the pattern by the old pattern. @@ -1886,16 +1862,16 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } else if (vim_strchr("uUlLeE", (uint8_t)(*src))) { switch (*src++) { case 'u': - func_one = (fptr_T)do_upper; + func_one = do_upper; continue; case 'U': - func_all = (fptr_T)do_Upper; + func_all = do_upper; continue; case 'l': - func_one = (fptr_T)do_lower; + func_one = do_lower; continue; case 'L': - func_all = (fptr_T)do_Lower; + func_all = do_lower; continue; case 'e': case 'E': @@ -1954,11 +1930,13 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } else { c = utf_ptr2char(src - 1); } + // Write to buffer, if copy is set. if (func_one != NULL) { - func_one = (fptr_T)(func_one(&cc, c)); + func_one(&cc, c); + func_one = NULL; } else if (func_all != NULL) { - func_all = (fptr_T)(func_all(&cc, c)); + func_all(&cc, c); } else { // just copy cc = c; @@ -2061,11 +2039,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen c = utf_ptr2char(s); if (func_one != (fptr_T)NULL) { - // Turbo C complains without the typecast - func_one = (fptr_T)(func_one(&cc, c)); + func_one(&cc, c); + func_one = NULL; } else if (func_all != (fptr_T)NULL) { - // Turbo C complains without the typecast - func_all = (fptr_T)(func_all(&cc, c)); + func_all(&cc, c); } else { // just copy cc = c; } diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 30c98a2f1e..4d318f8c59 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -153,13 +153,13 @@ void win_redr_status(win_T *wp) row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); col = is_stl_global ? 0 : wp->w_wincol; - int width = grid_puts(&default_grid, p, row, col, attr); + int width = grid_puts(&default_grid, p, -1, row, col, attr); grid_fill(&default_grid, row, row + 1, width + col, this_ru_col + col, fillchar, fillchar, attr); if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL) && this_ru_col - len > (int)(strlen(NameBuff) + 1)) { - grid_puts(&default_grid, NameBuff, row, + grid_puts(&default_grid, NameBuff, -1, row, (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } @@ -170,8 +170,8 @@ void win_redr_status(win_T *wp) const int sc_width = MIN(10, this_ru_col - len - 2); if (sc_width > 0) { - grid_puts_len(&default_grid, showcmd_buf, sc_width, row, - wp->w_wincol + this_ru_col - sc_width - 1, attr); + grid_puts(&default_grid, showcmd_buf, sc_width, row, + wp->w_wincol + this_ru_col - sc_width - 1, attr); } } } @@ -419,7 +419,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) int start_col = col; // Draw each snippet with the specified highlighting. - grid_puts_line_start(grid, row); + grid_line_start(grid, row); int curattr = attr; char *p = buf; @@ -427,7 +427,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) int textlen = (int)(hltab[n].start - p); // Make all characters printable. size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true); - col += grid_puts_len(grid, transbuf, (int)tsize, row, col, curattr); + col += grid_line_puts(col, transbuf, (int)tsize, curattr); p = hltab[n].start; if (hltab[n].userhl == 0) { @@ -442,13 +442,13 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } // Make sure to use an empty string instead of p, if p is beyond buf + len. size_t tsize = transstr_buf(p >= buf + len ? "" : p, -1, transbuf, sizeof transbuf, true); - col += grid_puts_len(grid, transbuf, (int)tsize, row, col, curattr); + col += grid_line_puts(col, transbuf, (int)tsize, curattr); int maxcol = start_col + maxwidth; // fill up with "fillchar" - grid_fill(grid, row, row + 1, col, maxcol, fillchar, fillchar, curattr); + grid_line_fill(col, maxcol, fillchar, curattr); - grid_puts_line_flush(false); + grid_line_flush(false); // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking // in the tab page line, status line or window bar @@ -618,7 +618,7 @@ void win_redr_ruler(win_T *wp) } ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, buffer, row, this_ru_col + off, attr); + grid_puts(grid, buffer, -1, row, this_ru_col + off, attr); grid_fill(grid, row, row + 1, this_ru_col + off + (int)strlen(buffer), off + width, fillchar, fillchar, attr); @@ -637,18 +637,7 @@ int fillchar_status(int *attr, win_T *wp) *attr = win_hl_attr(wp, HLF_SNC); fill = wp->w_p_fcs_chars.stlnc; } - // Use fill when there is highlighting, and highlighting of current - // window differs, or the fillchars differ, or this is not the - // current window - if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || ONE_WINDOW) - || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { - return fill; - } - if (is_curwin) { - return '^'; - } - return '='; + return fill; } /// Redraw the status line according to 'statusline' and take care of any @@ -810,12 +799,12 @@ void draw_tabline(void) if (col + len >= Columns - 3) { break; } - grid_puts_len(&default_grid, NameBuff, len, 0, col, - hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); + grid_puts(&default_grid, NameBuff, len, 0, col, + hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); col += len; } if (modified) { - grid_puts_len(&default_grid, "+", 1, 0, col++, attr); + grid_puts(&default_grid, "+", 1, 0, col++, attr); } grid_putchar(&default_grid, ' ', 0, col++, attr); } @@ -835,7 +824,7 @@ void draw_tabline(void) len = Columns - col - 1; } - grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr); + grid_puts(&default_grid, p, (int)strlen(p), 0, col, attr); col += len; } grid_putchar(&default_grid, ' ', 0, col++, attr); @@ -864,8 +853,8 @@ void draw_tabline(void) const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); if (sc_width > 0) { - grid_puts_len(&default_grid, showcmd_buf, sc_width, 0, - Columns - sc_width - (tabcount > 1) * 2, attr_nosel); + grid_puts(&default_grid, showcmd_buf, sc_width, 0, + Columns - sc_width - (tabcount > 1) * 2, attr_nosel); } } @@ -1583,7 +1572,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n // Note: The call will only return true if it actually // appended data to the `buf_tmp` buffer. - if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp))) { str = buf_tmp; } break; @@ -1663,7 +1652,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n char *p = NULL; if (fold) { - size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM)); + size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, + (linenr_T)get_vim_var_nr(VV_LNUM), NULL); stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1); p = out_p; p[n] = NUL; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0983667695..db15cdb053 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -24,6 +24,7 @@ #include "nvim/event/signal.h" #include "nvim/event/stream.h" #include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/grid_defs.h" #include "nvim/highlight_defs.h" #include "nvim/log.h" @@ -144,6 +145,7 @@ struct TUIData { } unibi_ext; char *space_buf; bool stopped; + int seen_error_exit; int width; int height; bool rgb; @@ -161,6 +163,7 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term) tui->is_starting = true; tui->screenshot = NULL; tui->stopped = false; + tui->seen_error_exit = 0; tui->loop = &main_loop; kv_init(tui->invalid_regions); signal_watcher_init(tui->loop, &tui->winch_handle, tui); @@ -383,8 +386,16 @@ static void terminfo_stop(TUIData *tui) unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); // May restore old title before exiting alternate screen. tui_set_title(tui, (String)STRING_INIT); - // Exit alternate screen. - unibi_out(tui, unibi_exit_ca_mode); + if (ui_client_exit_status == 0) { + ui_client_exit_status = tui->seen_error_exit; + } + // if nvim exited with nonzero status, without indicated this was an + // intentional exit (like `:1cquit`), it likely was an internal failure. + // Don't clobber the stderr error message in this case. + if (ui_client_exit_status == tui->seen_error_exit) { + // Exit alternate screen. + unibi_out(tui, unibi_exit_ca_mode); + } if (tui->cursor_color_changed) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } @@ -442,6 +453,11 @@ static void tui_terminal_stop(TUIData *tui) terminfo_stop(tui); } +void tui_error_exit(TUIData *tui, Integer status) +{ + tui->seen_error_exit = (int)status; +} + void tui_stop(TUIData *tui) { tui_terminal_stop(tui); @@ -675,15 +691,15 @@ static void final_column_wrap(TUIData *tui) /// It is undocumented, but in the majority of terminals and terminal emulators /// printing at the right margin does not cause an automatic wrap until the /// next character is printed, holding the cursor in place until then. -static void print_cell(TUIData *tui, UCell *ptr) +static void print_cell(TUIData *tui, char *buf, sattr_T attr) { UGrid *grid = &tui->grid; if (!tui->immediate_wrap_after_last_column) { // Printing the next character finally advances the cursor. final_column_wrap(tui); } - update_attrs(tui, ptr->attr); - out(tui, ptr->data, strlen(ptr->data)); + update_attrs(tui, attr); + out(tui, buf, strlen(buf)); grid->col++; if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. @@ -703,8 +719,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next) return false; } } - if (strlen(cell->data) > 1) { - return false; + if (schar_get_ascii(cell->data) == 0) { + return false; // not ascii } cell++; } @@ -831,14 +847,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool { UGrid *grid = &tui->grid; - if (grid->row == -1 && cell->data[0] == NUL) { + if (grid->row == -1 && cell->data == NUL) { // If cursor needs to repositioned and there is nothing to print, don't move cursor. return; } cursor_goto(tui, row, col); - bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell->data); + bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf)); if (is_ambiwidth && is_doublewidth) { // Clear the two screen cells. // If the character is single-width in the host terminal it won't change the second cell. @@ -847,7 +865,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool cursor_goto(tui, row, col); } - print_cell(tui, cell); + print_cell(tui, buf, cell->attr); if (is_ambiwidth) { // Force repositioning cursor after printing an ambiguous-width character. @@ -976,6 +994,8 @@ void tui_grid_clear(TUIData *tui, Integer g) { UGrid *grid = &tui->grid; ugrid_clear(grid); + // safe to clear cache at this point + schar_cache_clear_if_full(); kv_size(tui->invalid_regions) = 0; clear_region(tui, 0, tui->height, 0, tui->width, 0); } @@ -1273,7 +1293,7 @@ void tui_flush(TUIData *tui) int clear_col; for (clear_col = r.right; clear_col > 0; clear_col--) { UCell *cell = &grid->cells[row][clear_col - 1]; - if (!(cell->data[0] == ' ' && cell->data[1] == NUL + if (!(cell->data == schar_from_ascii(' ') && cell->attr == clear_attr)) { break; } @@ -1281,7 +1301,7 @@ void tui_flush(TUIData *tui) UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { print_cell_at_pos(tui, row, curcol, cell, - curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); + curcol < clear_col - 1 && (cell + 1)->data == NUL); }); if (clear_col < r.right) { clear_region(tui, row, row + 1, clear_col, r.right, clear_attr); @@ -1399,7 +1419,10 @@ void tui_screenshot(TUIData *tui, String path) for (int i = 0; i < grid->height; i++) { cursor_goto(tui, i, 0); for (int j = 0; j < grid->width; j++) { - print_cell(tui, &grid->cells[i][j]); + UCell cell = grid->cells[i][j]; + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, cell.data); + print_cell(tui, buf, cell.attr); } } flush_buf(tui); @@ -1446,13 +1469,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In { UGrid *grid = &tui->grid; for (Integer c = startcol; c < endcol; c++) { - memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T)); + grid->cells[linerow][c].data = chunk[c - startcol]; assert((size_t)attrs[c - startcol] < kv_size(tui->attrs)); grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { print_cell_at_pos(tui, (int)linerow, curcol, cell, - curcol < endcol - 1 && (cell + 1)->data[0] == NUL); + curcol < endcol - 1 && (cell + 1)->data == NUL); }); if (clearcol > endcol) { @@ -1469,7 +1492,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In if (endcol != grid->width) { // Print the last char of the row, if we haven't already done so. - int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1; + int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1; print_cell_at_pos(tui, (int)linerow, grid->width - size, &grid->cells[linerow][grid->width - size], size == 2); } diff --git a/src/nvim/types.h b/src/nvim/types.h index 3a05223023..afc1b3f5fe 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -46,4 +46,10 @@ typedef enum { typedef struct Decoration Decoration; +#ifndef ORDER_BIG_ENDIAN +# if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define ORDER_BIG_ENDIAN +# endif +#endif + #endif // NVIM_TYPES_H diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index be1746983c..d3a1097aba 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -4,6 +4,7 @@ #include <assert.h> #include <string.h> +#include "nvim/grid.h" #include "nvim/memory.h" #include "nvim/ugrid.h" @@ -79,8 +80,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat { for (int row = top; row <= bot; row++) { UGRID_FOREACH_CELL(grid, row, left, right + 1, { - cell->data[0] = ' '; - cell->data[1] = 0; + cell->data = schar_from_ascii(' '); cell->attr = attr; }); } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index a85a6fb4a8..1c73c43867 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -11,10 +11,8 @@ struct ugrid; typedef struct ucell UCell; typedef struct ugrid UGrid; -#define CELLBYTES (sizeof(schar_T)) - struct ucell { - char data[CELLBYTES + 1]; + schar_T data; sattr_T attr; }; diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h index 7e5f847039..05964422f3 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -23,6 +23,9 @@ EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); // ID of the ui client channel. If zero, the client is not running. EXTERN uint64_t ui_client_channel_id INIT(= 0); +// exit status from embedded nvim process +EXTERN int ui_client_exit_status INIT(= 0); + // TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. // This will be restructured as part of The UI Devirtualization Project. diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index e9b23d1298..b88c355f42 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -55,7 +55,7 @@ static int msg_current_row = INT_MAX; static bool msg_was_scrolled = false; static int msg_sep_row = -1; -static schar_T msg_sep_char = { ' ', NUL }; +static schar_T msg_sep_char = schar_from_ascii(' '); static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; @@ -354,7 +354,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag grid = &msg_grid; sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP); for (int i = col; i < until; i++) { - memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf)); + linebuf[i - startcol] = msg_sep_char; attrbuf[i - startcol] = msg_sep_attr; } } else { @@ -363,9 +363,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf)); memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf)); if (grid->comp_col + grid->cols > until - && grid->chars[off + n][0] == NUL) { - linebuf[until - 1 - startcol][0] = ' '; - linebuf[until - 1 - startcol][1] = '\0'; + && grid->chars[off + n] == NUL) { + linebuf[until - 1 - startcol] = schar_from_ascii(' '); if (col == startcol && n == 1) { skipstart = 0; } @@ -378,10 +377,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag for (int i = col - (int)startcol; i < until - startcol; i += width) { width = 1; // negative space - bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL; - if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) { + bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL; + if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) { width = 2; - thru &= strequal((char *)linebuf[i + 1], " "); + thru &= linebuf[i + 1] == schar_from_ascii(' '); } attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru); if (width == 2) { @@ -396,28 +395,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag // Tricky: if overlap caused a doublewidth char to get cut-off, must // replace the visible half with a space. - if (linebuf[col - startcol][0] == NUL) { - linebuf[col - startcol][0] = ' '; - linebuf[col - startcol][1] = NUL; + if (linebuf[col - startcol] == NUL) { + linebuf[col - startcol] = schar_from_ascii(' '); if (col == endcol - 1) { skipend = 0; } - } else if (col == startcol && n > 1 && linebuf[1][0] == NUL) { + } else if (col == startcol && n > 1 && linebuf[1] == NUL) { skipstart = 0; } col = until; } - if (linebuf[endcol - startcol - 1][0] == NUL) { + if (linebuf[endcol - startcol - 1] == NUL) { skipend = 0; } assert(endcol <= chk_width); assert(row < chk_height); - if (!(grid && grid == &default_grid)) { - // TODO(bfredl): too conservative, need check - // grid->line_wraps if grid->Width == Width + if (!(grid && (grid == &default_grid || (grid->comp_col == 0 && grid->cols == Columns)))) { flags = flags & ~kLineFlagWrap; } @@ -568,7 +564,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep if (scrolled && row > 0) { msg_sep_row = (int)row - 1; if (sep_char.data) { - xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); + msg_sep_char = schar_from_buf(sep_char.data, sep_char.size); } } else { msg_sep_row = -1; diff --git a/src/nvim/version.c b/src/nvim/version.c index b6861142cc..0744a601bb 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2837,12 +2837,6 @@ void intro_message(int colon) } } } - - // Make the wait-return message appear just below the text. - if (colon) { - assert(row <= INT_MAX); - msg_row = (int)row; - } } static void do_intro_line(long row, char *mesg, int attr) @@ -2871,8 +2865,8 @@ static void do_intro_line(long row, char *mesg, int attr) l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); - grid_puts_len(&default_grid, p, l, (int)row, (int)col, - *p == '<' ? HL_ATTR(HLF_8) : attr); + grid_puts(&default_grid, p, l, (int)row, (int)col, + *p == '<' ? HL_ATTR(HLF_8) : attr); col += clen; } } diff --git a/src/nvim/window.c b/src/nvim/window.c index c0d399c4c4..1208494eaf 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1009,7 +1009,9 @@ void ui_ext_win_position(win_T *wp, bool validate) comp_row += grid->comp_row; comp_col += grid->comp_col; comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - (p_ch > 0 ? 1 : 0)), 0); - comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0); + if (!c.fixed || east) { + comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0); + } wp->w_winrow = comp_row; wp->w_wincol = comp_col; ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, diff --git a/test/README.md b/test/README.md index 42d0ec0323..3aafe1273e 100644 --- a/test/README.md +++ b/test/README.md @@ -102,9 +102,25 @@ Debugging tests DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file) INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0 ``` -- You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527). - And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of +- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527): + + ```sh + GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest + ``` + + Read more about [filtering tests](#filtering-tests). + + Then, in another terminal: + + ```sh + gdb -ex 'target remote localhost:7777' build/bin/nvim + ``` + + If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of starting gdbserver directly. + + See [test/functional/helpers.lua](https://github.com/neovim/neovim/blob/9cadbf1d36b63f53f0de48c8c5ff6c752ff05d70/test/functional/helpers.lua#L52-L69) for details. + - Hanging tests can happen due to unexpected "press-enter" prompts. The default screen width is 50 columns. Commands that try to print lines longer than 50 columns in the command-line, e.g. `:edit very...long...path`, will @@ -124,7 +140,7 @@ Filtering Tests ### Filter by name -Another filter method is by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. +Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. ``` lua it('foo api',function() diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index c4449bc201..133ec67942 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1524,16 +1524,19 @@ describe('API/extmarks', function() sign_hl_group = "Statement", sign_text = ">>", spell = true, - virt_lines = { { { "lines", "Statement" } }}, + virt_lines = { + { { "lines", "Macro" }, { "???" } }, + { { "stack", { "Type", "Search" } }, { "!!!" } }, + }, virt_lines_above = true, virt_lines_leftcol = true, - virt_text = { { "text", "Statement" } }, + virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } }, virt_text_hide = true, virt_text_pos = "right_align", }) set_extmark(ns, marks[2], 0, 0, { priority = 0, - virt_text = { { "text", "Statement" } }, + virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } }, virt_text_win_col = 1, }) eq({0, 0, { @@ -1553,10 +1556,13 @@ describe('API/extmarks', function() sign_hl_group = "Statement", sign_text = ">>", spell = true, - virt_lines = { { { "lines", "Statement" } }}, + virt_lines = { + { { "lines", "Macro" }, { "???" } }, + { { "stack", { "Type", "Search" } }, { "!!!" } }, + }, virt_lines_above = true, virt_lines_leftcol = true, - virt_text = { { "text", "Statement" } }, + virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } }, virt_text_hide = true, virt_text_pos = "right_align", } }, get_extmark_by_id(ns, marks[1], { details = true })) @@ -1564,7 +1570,7 @@ describe('API/extmarks', function() ns_id = 1, right_gravity = true, priority = 0, - virt_text = { { "text", "Statement" } }, + virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } }, virt_text_hide = false, virt_text_pos = "win_col", virt_text_win_col = 1, diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f734bfb0c0..09495fbaac 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1350,12 +1350,59 @@ describe('API', function() end) it('nvim_get_vvar, nvim_set_vvar', function() - -- Set readonly v: var. - eq('Key is read-only: count', - pcall_err(request, 'nvim_set_vvar', 'count', 42)) - -- Set writable v: var. + eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42)) + eq('Dictionary is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42)) meths.set_vvar('errmsg', 'set by API') eq('set by API', meths.get_vvar('errmsg')) + meths.set_vvar('errmsg', 42) + eq('42', eval('v:errmsg')) + meths.set_vvar('oldfiles', { 'one', 'two' }) + eq({ 'one', 'two' }, eval('v:oldfiles')) + meths.set_vvar('oldfiles', {}) + eq({}, eval('v:oldfiles')) + eq('Setting v:oldfiles to value with wrong type', pcall_err(meths.set_vvar, 'oldfiles', 'a')) + eq({}, eval('v:oldfiles')) + + feed('i foo foo foo<Esc>0/foo<CR>') + eq({1, 1}, meths.win_get_cursor(0)) + eq(1, eval('v:searchforward')) + feed('n') + eq({1, 5}, meths.win_get_cursor(0)) + meths.set_vvar('searchforward', 0) + eq(0, eval('v:searchforward')) + feed('n') + eq({1, 1}, meths.win_get_cursor(0)) + meths.set_vvar('searchforward', 1) + eq(1, eval('v:searchforward')) + feed('n') + eq({1, 5}, meths.win_get_cursor(0)) + + local screen = Screen.new(60, 3) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {background = Screen.colors.Yellow}, + }) + screen:attach() + eq(1, eval('v:hlsearch')) + screen:expect{grid=[[ + {1:foo} {1:^foo} {1:foo} | + {0:~ }| + | + ]]} + meths.set_vvar('hlsearch', 0) + eq(0, eval('v:hlsearch')) + screen:expect{grid=[[ + foo ^foo foo | + {0:~ }| + | + ]]} + meths.set_vvar('hlsearch', 1) + eq(1, eval('v:hlsearch')) + screen:expect{grid=[[ + {1:foo} {1:^foo} {1:foo} | + {0:~ }| + | + ]]} end) it('vim_set_var returns the old value', function() @@ -2006,6 +2053,19 @@ describe('API', function() end) describe('nvim_out_write', function() + local screen + + before_each(function() + screen = Screen.new(40, 8) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Blue}, + }) + end) + it('prints long messages correctly #20534', function() exec([[ set more @@ -2026,14 +2086,7 @@ describe('API', function() eq('\naaa\n' .. ('a'):rep(5002) .. '\naaa', meths.get_var('out')) end) - it('blank line in message works', function() - local screen = Screen.new(40, 8) - screen:attach() - screen:set_default_attr_ids({ - [0] = {bold = true, foreground = Screen.colors.Blue}, - [1] = {bold = true, foreground = Screen.colors.SeaGreen}, - [2] = {bold = true, reverse = true}, - }) + it('blank line in message', function() feed([[:call nvim_out_write("\na\n")<CR>]]) screen:expect{grid=[[ | @@ -2058,6 +2111,20 @@ describe('API', function() {1:Press ENTER or type command to continue}^ | ]]} end) + + it('NUL bytes in message', function() + feed([[:lua vim.api.nvim_out_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')<CR>]]) + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {2: }| + aaa{3:^@}bbb{3:^@^@}ccc | + ddd{3:^@^@^@}eee | + {1:Press ENTER or type command to continue}^ | + ]]} + end) end) describe('nvim_err_write', function() @@ -2146,6 +2213,20 @@ describe('API', function() ]]) feed('<cr>') -- exit the press ENTER screen end) + + it('NUL bytes in message', function() + nvim_async('err_write', 'aaa\0bbb\0\0ccc\nddd\0\0\0eee\n') + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {3: }| + {1:aaa^@bbb^@^@ccc} | + {1:ddd^@^@^@eee} | + {2:Press ENTER or type command to continue}^ | + ]]} + end) end) describe('nvim_err_writeln', function() diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 44d4470337..6737c2d15b 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -895,13 +895,14 @@ describe('API/win', function() it('includes border', function() local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' } local win = meths.open_win(0, true, { - relative='win', row=3, col=3, width=12, height=3, - border = b, + relative='win', row=3, col=3, width=12, height=3, + border = b, }) local cfg = meths.win_get_config(win) eq(b, cfg.border) end) + it('includes border with highlight group', function() local b = { {'a', 'Normal'}, @@ -914,12 +915,25 @@ describe('API/win', function() {'h', 'PreProc'}, } local win = meths.open_win(0, true, { - relative='win', row=3, col=3, width=12, height=3, - border = b, + relative='win', row=3, col=3, width=12, height=3, + border = b, }) local cfg = meths.win_get_config(win) eq(b, cfg.border) end) + + it('includes title and footer', function() + local title = { {'A', {'StatusLine', 'TabLine'}}, {'B'}, {'C', 'WinBar'} } + local footer = { {'A', 'WinBar'}, {'B'}, {'C', {'StatusLine', 'TabLine'}} } + local win = meths.open_win(0, true, { + relative='win', row=3, col=3, width=12, height=3, + border = 'single', title = title, footer = footer, + }) + + local cfg = meths.win_get_config(win) + eq(title, cfg.title) + eq(footer, cfg.footer) + end) end) end) diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 206838408f..3e0f6d1fcc 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -17,10 +17,14 @@ local pesc = helpers.pesc local os_kill = helpers.os_kill local set_session = helpers.set_session local spawn = helpers.spawn -local nvim_async = helpers.nvim_async +local async_meths = helpers.async_meths local expect_msg_seq = helpers.expect_msg_seq local pcall_err = helpers.pcall_err local mkdir = helpers.mkdir +local poke_eventloop = helpers.poke_eventloop +local meths = helpers.meths +local retry = helpers.retry +local write_file = helpers.write_file describe(':recover', function() before_each(clear) @@ -49,10 +53,10 @@ describe("preserve and (R)ecover with custom 'directory'", function() set swapfile fileformat=unix undolevels=-1 ]] - local nvim1 + local nvim0 before_each(function() - nvim1 = spawn(new_argv()) - set_session(nvim1) + nvim0 = spawn(new_argv()) + set_session(nvim0) rmdir(swapdir) mkdir(swapdir) end) @@ -71,8 +75,7 @@ describe("preserve and (R)ecover with custom 'directory'", function() local function test_recover(swappath1) -- Start another Nvim instance. - local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, - true) + local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) set_session(nvim2) exec(init) @@ -103,23 +106,23 @@ describe("preserve and (R)ecover with custom 'directory'", function() it('closing stdio channel without :preserve #22096', function() local swappath1 = setup_swapname() - nvim1:close() + nvim0:close() test_recover(swappath1) end) it('killing TUI process without :preserve #22096', function() helpers.skip(helpers.is_os('win')) - local screen = Screen.new() - screen:attach() + local screen0 = Screen.new() + screen0:attach() local child_server = new_pipename() funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server}) - screen:expect({any = pesc('[No Name]')}) -- Wait for the child process to start. + screen0:expect({any = pesc('[No Name]')}) -- Wait for the child process to start. local child_session = helpers.connect(child_server) set_session(child_session) local swappath1 = setup_swapname() - set_session(nvim1) + set_session(nvim0) command('call chanclose(&channel)') -- Kill the child process. - screen:expect({any = pesc('[Process exited 1]')}) -- Wait for the child process to stop. + screen0:expect({any = pesc('[Process exited 1]')}) -- Wait for the child process to stop. test_recover(swappath1) end) @@ -200,7 +203,7 @@ describe('swapfile detection', function() screen2:expect(expected_no_dialog) -- With API call and shortmess+=F - nvim_async('command', 'edit %') + async_meths.command('edit %') screen2:expect{any=[[Found a swap file by the name ".*]] ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]} feed('e') -- Chose "Edit" at the swap dialog. @@ -373,3 +376,91 @@ describe('swapfile detection', function() feed('e') end) end) + +describe('quitting swapfile dialog on startup stops TUI properly', function() + local swapdir = luv.cwd()..'/Xtest_swapquit_dir' + local testfile = 'Xtest_swapquit_file1' + local otherfile = 'Xtest_swapquit_file2' + -- Put swapdir at the start of the 'directory' list. #1836 + -- Note: `set swapfile` *must* go after `set directory`: otherwise it may + -- attempt to create a swapfile in different directory. + local init_dir = [[set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//]] + local init_set = [[set swapfile fileformat=unix nomodified undolevels=-1 nohidden]] + + before_each(function() + clear({args = {'--cmd', init_dir, '--cmd', init_set}}) + rmdir(swapdir) + mkdir(swapdir) + write_file(testfile, [[ + first + second + third + + ]]) + command('edit! '..testfile) + feed('Gisometext<esc>') + poke_eventloop() + clear() -- Leaves a swap file behind + meths.ui_attach(80, 30, {}) + end) + after_each(function() + rmdir(swapdir) + os.remove(testfile) + os.remove(otherfile) + end) + + it('(Q)uit at first file argument', function() + local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', + '--cmd', init_dir, '--cmd', init_set, + testfile}) + retry(nil, nil, function() + eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:', + eval("getline('$')->trim(' ', 2)")) + end) + meths.chan_send(chan, 'q') + retry(nil, nil, function() + eq({'', '[Process exited 1]', ''}, + eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})")) + end) + end) + + it('(A)bort at second file argument with -p', function() + local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', + '--cmd', init_dir, '--cmd', init_set, + '-p', otherfile, testfile}) + retry(nil, nil, function() + eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:', + eval("getline('$')->trim(' ', 2)")) + end) + meths.chan_send(chan, 'a') + retry(nil, nil, function() + eq({'', '[Process exited 1]', ''}, + eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})")) + end) + end) + + it('(Q)uit at file opened by -t', function() + write_file(otherfile, ([[ + !_TAG_FILE_ENCODING utf-8 // + first %s /^ \zsfirst$/ + second %s /^ \zssecond$/ + third %s /^ \zsthird$/]]):format(testfile, testfile, testfile)) + local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', + '--cmd', init_dir, '--cmd', init_set, + '--cmd', 'set tags='..otherfile, '-tsecond'}) + retry(nil, nil, function() + eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:', + eval("getline('$')->trim(' ', 2)")) + end) + meths.chan_send(chan, 'q') + retry(nil, nil, function() + eq('Press ENTER or type command to continue', + eval("getline('$')->trim(' ', 2)")) + end) + meths.chan_send(chan, '\r') + retry(nil, nil, function() + eq({'', '[Process exited 1]', ''}, + eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})")) + end) + end) +end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index ef87f6c21a..0db9265a29 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -949,6 +949,28 @@ function tests.set_defaults_all_capabilities() } end +function tests.inlay_hint() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + inlayHintProvider = true; + } + } + end; + body = function() + notify('start') + expect_request('textDocument/inlayHint', function() + return nil, {} + end) + expect_notification("finish") + notify('finish') + end; + } +end + -- Tests will be indexed by test_name local test_name = arg[1] local timeout = arg[2] diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 1dfb9a5e10..bd3d8f5247 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1491,6 +1491,60 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.v.null") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) + eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]])) + eq('Dictionary is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) + eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) + exec_lua([[vim.v.errmsg = 'set by Lua']]) + eq('set by Lua', eval('v:errmsg')) + exec_lua([[vim.v.errmsg = 42]]) + eq('42', eval('v:errmsg')) + exec_lua([[vim.v.oldfiles = { 'one', 'two' }]]) + eq({ 'one', 'two' }, eval('v:oldfiles')) + exec_lua([[vim.v.oldfiles = {}]]) + eq({}, eval('v:oldfiles')) + eq('Setting v:oldfiles to value with wrong type', pcall_err(exec_lua, [[vim.v.oldfiles = 'a']])) + eq({}, eval('v:oldfiles')) + + feed('i foo foo foo<Esc>0/foo<CR>') + eq({1, 1}, meths.win_get_cursor(0)) + eq(1, eval('v:searchforward')) + feed('n') + eq({1, 5}, meths.win_get_cursor(0)) + exec_lua([[vim.v.searchforward = 0]]) + eq(0, eval('v:searchforward')) + feed('n') + eq({1, 1}, meths.win_get_cursor(0)) + exec_lua([[vim.v.searchforward = 1]]) + eq(1, eval('v:searchforward')) + feed('n') + eq({1, 5}, meths.win_get_cursor(0)) + + local screen = Screen.new(60, 3) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {background = Screen.colors.Yellow}, + }) + screen:attach() + eq(1, eval('v:hlsearch')) + screen:expect{grid=[[ + {1:foo} {1:^foo} {1:foo} | + {0:~ }| + | + ]]} + exec_lua([[vim.v.hlsearch = 0]]) + eq(0, eval('v:hlsearch')) + screen:expect{grid=[[ + foo ^foo foo | + {0:~ }| + | + ]]} + exec_lua([[vim.v.hlsearch = 1]]) + eq(1, eval('v:hlsearch')) + screen:expect{grid=[[ + {1:foo} {1:^foo} {1:foo} | + {0:~ }| + | + ]]} end) it('vim.bo', function() diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index f544255d81..12763cfef5 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -88,6 +88,40 @@ describe('vim.lsp.util', function() end) end) + describe('normalize_markdown', function () + it('collapses consecutive blank lines', function () + local result = exec_lua [[ + local lines = { + 'foo', + '', + '', + '', + 'bar', + '', + 'baz' + } + return vim.lsp.util._normalize_markdown(lines) + ]] + local expected = {'foo', '', 'bar', '', 'baz'} + eq(expected, result) + end) + + it('removes preceding and trailing empty lines', function () + local result = exec_lua [[ + local lines = { + '', + 'foo', + 'bar', + '', + '' + } + return vim.lsp.util._normalize_markdown(lines) + ]] + local expected = {'foo', 'bar'} + eq(expected, result) + end) + end) + describe("make_floating_popup_options", function () local function assert_anchor(anchor_bias, expected_anchor) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e0a8badb67..155c9ad96c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1244,6 +1244,67 @@ describe('LSP', function() } end) + it('should send correct range for inlay hints with noeol', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, { + method="textDocument/inlayHint", + params = { + textDocument = { + uri = 'file://', + }, + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 1, character = 3 }, + } + }, + bufnr=2, + client_id=1, + }}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "inlay_hint"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + vim.bo[BUFFER].eol = false + ]] + end; + on_init = function(_client) + client = _client + eq(true, client.supports_method('textDocument/inlayHint')) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.lsp.inlay_hint(BUFFER, true) + ]] + end + if ctx.method == 'textDocument/inlayHint' then + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; @@ -3032,7 +3093,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) ]] - local expected = {'```cs', 'TestEntity.TestEntity()', '```', '<text>', 'some doc', '</text>'} + local expected = {'```cs', 'TestEntity.TestEntity()', '```', 'some doc'} eq(expected, result) end) end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 4a3005e082..1e278e4cff 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -5,7 +5,6 @@ local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, testprg = helpers.feed, helpers.testprg local eval = helpers.eval local command = helpers.command -local matches = helpers.matches local poke_eventloop = helpers.poke_eventloop local retry = helpers.retry local meths = helpers.meths @@ -457,8 +456,8 @@ describe("'scrollback' option", function() expect_lines(58) -- Verify off-screen state - matches((is_os('win') and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) - matches((is_os('win') and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) + eq((is_os('win') and '36: line' or '35: line'), eval("getline(line('w0') - 1)->trim(' ', 2)")) + eq((is_os('win') and '27: line' or '26: line'), eval("getline(line('w0') - 10)->trim(' ', 2)")) end) it('deletes extra lines immediately', function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 57dcb14cf8..53ee4c51f4 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -29,7 +29,7 @@ local spawn_argv = helpers.spawn_argv local set_session = helpers.set_session local write_file = helpers.write_file -if helpers.skip(helpers.is_os('win')) then return end +if helpers.skip(is_os('win')) then return end describe('TUI', function() local screen @@ -67,8 +67,7 @@ describe('TUI', function() local function expect_child_buf_lines(expected) assert(type({}) == type(expected)) retry(nil, nil, function() - local _, buflines = child_session:request( - 'nvim_buf_get_lines', 0, 0, -1, false) + local _, buflines = child_session:request('nvim_buf_get_lines', 0, 0, -1, false) eq(expected, buflines) end) end @@ -319,13 +318,13 @@ describe('TUI', function() local attrs = screen:get_default_attr_ids() attrs[11] = {foreground = 81} screen:expect([[ - {11:^G^V^M}{1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] [+] }| - {3:-- INSERT --} | - {3:-- TERMINAL --} | + {11:^G^V^M}{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | ]], attrs) end) @@ -949,8 +948,7 @@ describe('TUI', function() ]]) -- Dot-repeat/redo. feed_data('2.') - expect_child_buf_lines( - {'"pasted from terminapasted from terminalpasted from terminall"'}) + expect_child_buf_lines({'"pasted from terminapasted from terminalpasted from terminall"'}) screen:expect([[ "pasted from terminapasted from terminalpasted fro| m termina{1:l}l" | @@ -1003,13 +1001,13 @@ describe('TUI', function() 'this is line 2', 'line 3 is here', '', - } + } -- Redo. feed_data('\18') -- <C-r> expect_child_buf_lines{ 'thisjust paste it™3 is here', '', - } + } end) it('paste: terminal mode', function() @@ -1071,17 +1069,15 @@ describe('TUI', function() screen:expect{grid=expected_grid1, attr_ids=expected_attr} -- Dot-repeat/redo. feed_data('.') - screen:expect{ - grid=[[ - ESC:{11:^[} / CR: | - xline 1 | - ESC:{11:^[} / CR: | - {1:x} | - {5:[No Name] [+] 5,1 Bot}| - | - {3:-- TERMINAL --} | - ]], - attr_ids=expected_attr} + screen:expect{grid=[[ + ESC:{11:^[} / CR: | + xline 1 | + ESC:{11:^[} / CR: | + {1:x} | + {5:[No Name] [+] 5,1 Bot}| + | + {3:-- TERMINAL --} | + ]], attr_ids=expected_attr} -- Undo. feed_data('u') expect_child_buf_lines(expected_crlf) @@ -1368,8 +1364,7 @@ describe('TUI', function() {5:[No Name] [+] }| {3:-- INSERT --} | {3:-- TERMINAL --} | - ]], - attr_ids={ + ]], attr_ids={ [1] = {reverse = true}, [2] = {background = tonumber('0x00000b')}, [3] = {bold = true}, @@ -1548,7 +1543,7 @@ describe('TUI', function() it('forwards :term palette colors with termguicolors', function() if is_ci('github') then - pending("tty-test complains about not owning the terminal -- actions/runner#241") + pending("tty-test complains about not owning the terminal -- actions/runner#241") end screen:set_rgb_cterm(true) screen:set_default_attr_ids({ @@ -1599,26 +1594,26 @@ describe('TUI', function() local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color' local expected = { { - chan = 1, - ext_cmdline = false, - ext_hlstate = false, - ext_linegrid = true, - ext_messages = false, - ext_multigrid = false, - ext_popupmenu = false, - ext_tabline = false, - ext_termcolors = true, - ext_wildmenu = false, - height = 6, - override = false, - rgb = false, - stdin_tty = true, - stdout_tty = true, - term_background = '', - term_colors = 256, - term_name = exp_term, - width = 50 - }, + chan = 1, + ext_cmdline = false, + ext_hlstate = false, + ext_linegrid = true, + ext_messages = false, + ext_multigrid = false, + ext_popupmenu = false, + ext_tabline = false, + ext_termcolors = true, + ext_wildmenu = false, + height = 6, + override = false, + rgb = false, + stdin_tty = true, + stdout_tty = true, + term_background = '', + term_colors = 256, + term_name = exp_term, + width = 50 + }, } local _, rv = child_session:request('nvim_list_uis') eq(expected, rv) @@ -1639,8 +1634,8 @@ describe('TUI', function() | {3:-- TERMINAL --} | ]] - -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, the - -- second cell of "℃" is a space and the attributes of the "℃" are applied to it. + -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, + -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| @@ -1662,22 +1657,19 @@ describe('TUI', function() end) it('draws correctly when cursor_address overflows #21643', function() - helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS') - screen:try_resize(77, 834) + helpers.skip(is_os('mac'), 'FIXME: crashes/errors on macOS') + screen:try_resize(77, 855) retry(nil, nil, function() - eq({true, 831}, {child_session:request('nvim_win_get_height', 0)}) + eq({true, 852}, {child_session:request('nvim_win_get_height', 0)}) end) -- Use full screen message so that redrawing afterwards is more deterministic. child_session:notify('nvim_command', 'intro') screen:expect({any = 'Nvim'}) -- Going to top-left corner needs 3 bytes. -- Setting underline attribute needs 9 bytes. - -- With screen width 77, 63857 characters need 829 full screen lines. - -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF). - -- The incomplete screen line needs 24 + 3 = 27 bytes. - -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes. + -- The whole line needs 3 + 9 + 65515 + 3 = 65530 bytes. -- The cursor_address that comes after will overflow the 65535-byte buffer. - local line = ('a'):rep(63857) .. '℃' + local line = ('a'):rep(65515) .. '℃' child_session:notify('nvim_exec_lua', [[ vim.api.nvim_buf_set_lines(0, 0, -1, true, {...}) vim.o.cursorline = true @@ -1686,8 +1678,8 @@ describe('TUI', function() feed_data('\n') screen:expect( '{13:a}{12:' .. ('a'):rep(76) .. '}|\n' - .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828) - .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[ + .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(849) + .. '{12:' .. ('a'):rep(65) .. '℃' .. (' '):rep(11) .. '}|\n' .. dedent([[ b | {5:[No Name] [+] }| | @@ -1723,7 +1715,15 @@ describe('TUI', function() it('no assert failure on deadly signal #21896', function() exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]]) - screen:expect({any = '%[Process exited 1%]'}) + screen:expect{grid=[[ + Vim: Caught deadly signal 'SIGTERM' | + | + | + [Process exited 1]{1: } | + | + | + {3:-- TERMINAL --} | + ]]} end) it('no stack-use-after-scope with cursor color #22432', function() @@ -2168,7 +2168,7 @@ describe("TUI 't_Co' (terminal colors)", function() local screen local function assert_term_colors(term, colorterm, maxcolors) - helpers.clear({env={TERM=term}, args={}}) + clear({env={TERM=term}, args={}}) -- This is ugly because :term/termopen() forces TERM=xterm-256color. -- TODO: Revisit this after jobstart/termopen accept `env` dict. screen = thelpers.screen_setup(0, string.format( @@ -2533,29 +2533,28 @@ describe("TUI", function() end) +-- See test/unit/tui_spec.lua for unit tests. describe('TUI bg color', function() local screen - local function setup() - -- Only single integration test. - -- See test/unit/tui_spec.lua for unit tests. + local function setup_bg_test() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", ' ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]') end - before_each(setup) + before_each(setup_bg_test) it('triggers OptionSet event on unsplit terminal-response', function() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) feed_data('\027]11;rgb:ffff/ffff/ffff\007') screen:expect{any='did OptionSet, yay!'} @@ -2563,15 +2562,15 @@ describe('TUI bg color', function() feed_data(':echo "new_bg=".&background\n') screen:expect{any='new_bg=light'} - setup() + setup_bg_test() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) feed_data('\027]11;rgba:ffff/ffff/ffff/8000\027\\') screen:expect{any='did OptionSet, yay!'} @@ -2582,13 +2581,13 @@ describe('TUI bg color', function() it('triggers OptionSet event with split terminal-response', function() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) -- Send a background response with the OSC command part split. feed_data('\027]11;rgb') @@ -2598,15 +2597,15 @@ describe('TUI bg color', function() feed_data(':echo "new_bg=".&background\n') screen:expect{any='new_bg=light'} - setup() + setup_bg_test() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) -- Send a background response with the Pt portion split. feed_data('\027]11;rgba:ffff/fff') @@ -2619,13 +2618,13 @@ describe('TUI bg color', function() it('not triggers OptionSet event with invalid terminal-response', function() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) feed_data('\027]11;rgb:ffff/ffff/ffff/8000\027\\') screen:expect_unchanged() @@ -2633,15 +2632,15 @@ describe('TUI bg color', function() feed_data(':echo "new_bg=".&background\n') screen:expect{any='new_bg=dark'} - setup() + setup_bg_test() screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] 0,0-1 All}| - | - {3:-- TERMINAL --} | + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | ]]) feed_data('\027]11;rgba:ffff/foo/ffff/8000\007') screen:expect_unchanged() @@ -2721,7 +2720,7 @@ describe("TUI as a client", function() end) it("connects to remote instance (--headless)", function() - local server = helpers.spawn_argv(false) -- equivalent to clear() + local server = spawn_argv(false) -- equivalent to clear() local client_super = spawn_argv(true) set_session(server) @@ -2729,11 +2728,11 @@ describe("TUI as a client", function() server:request('nvim_input', 'iHalloj!<Esc>') set_session(client_super) - local screen = thelpers.screen_setup(0, + local screen_client = thelpers.screen_setup(0, string.format([=[["%s", "--server", "%s", "--remote-ui"]]=], nvim_prog, server_pipe)) - screen:expect{grid=[[ + screen_client:expect{grid=[[ Halloj{1:!} | {4:~ }| {4:~ }| @@ -2746,7 +2745,15 @@ describe("TUI as a client", function() -- No heap-use-after-free when receiving UI events after deadly signal #22184 server:request('nvim_input', ('a'):rep(1000)) exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]]) - screen:expect({any = '%[Process exited 1%]'}) + screen_client:expect{grid=[[ + Vim: Caught deadly signal 'SIGTERM' | + | + | + [Process exited 1]{1: } | + | + | + {3:-- TERMINAL --} | + ]]} eq(0, meths.get_vvar('shell_error')) -- exits on input eof #22244 @@ -2774,7 +2781,7 @@ describe("TUI as a client", function() ]]) end) - it("exits when server quits", function() + local function test_remote_tui_quit(status) local server_super = spawn_argv(false) -- equivalent to clear() local client_super = spawn_argv(true) @@ -2783,6 +2790,15 @@ describe("TUI as a client", function() local screen_server = thelpers.screen_setup(0, string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], nvim_prog, server_pipe, nvim_set)) + screen_server:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data("iHello, World") screen_server:expect{grid=[[ @@ -2822,13 +2838,39 @@ describe("TUI as a client", function() -- quitting the server set_session(server_super) - feed_data(":q!\n") - screen_server:expect({any="Process exited 0"}) - + feed_data(status and ':' .. status .. 'cquit!\n' or ":quit!\n") + status = status and status or 0 + screen_server:expect{grid=[[ + | + [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + | + | + | + | + {3:-- TERMINAL --} | + ]]} -- assert that client has exited - screen_client:expect({any="Process exited 0"}) + screen_client:expect{grid=[[ + | + [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + | + | + | + | + {3:-- TERMINAL --} | + ]]} server_super:close() client_super:close() + end + + describe("exits when server quits", function() + it("with :quit", function() + test_remote_tui_quit() + end) + + it("with :cquit", function() + test_remote_tui_quit(42) + end) end) end) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 0aa0cdd6d6..e037c9e215 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -838,3 +838,67 @@ describe('treesitter highlighting (help)', function() end) end) + +describe('treesitter highlighting (nested injections)', function() + local screen + + before_each(function() + screen = Screen.new(80, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.SlateBlue}; + [2] = {bold = true, foreground = Screen.colors.Brown}; + [3] = {foreground = Screen.colors.Cyan4}; + [4] = {foreground = Screen.colors.Fuchsia}; + } + end) + + it("correctly redraws nested injections (GitHub #25252)", function() + insert[=[ +function foo() print("Lua!") end + +local lorem = { + ipsum = {}, + bar = {}, +} +vim.cmd([[ + augroup RustLSP + autocmd CursorHold silent! lua vim.lsp.buf.document_highlight() + augroup END +]]) + ]=] + + exec_lua [[ + vim.opt.scrolloff = 0 + vim.bo.filetype = 'lua' + vim.treesitter.start() + ]] + + -- invalidate the language tree + feed("ggi--[[<ESC>04x") + + screen:expect{grid=[[ + {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + + -- spam newline insert/delete to invalidate Lua > Vim > Lua region + feed("3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0") + + screen:expect{grid=[[ + {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + ^ {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + end) + +end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 4b92ab730d..497b2e7f4c 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1021,6 +1021,26 @@ describe('cmdheight=0', function() screen:attach() end) + it("with redrawdebug=invalid resize -1", function() + command("set redrawdebug=invalid cmdheight=0 noruler laststatus=0") + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + feed(":resize -1<CR>") + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + assert_alive() + end) + it("with cmdheight=1 noruler laststatus=2", function() command("set cmdheight=1 noruler laststatus=2") screen:expect{grid=[[ @@ -1275,6 +1295,7 @@ describe('cmdheight=0', function() it('with multigrid', function() clear{args={'--cmd', 'set cmdheight=0'}} screen:attach{ext_multigrid=true} + meths.buf_set_lines(0, 0, -1, true, {'p'}) screen:expect{grid=[[ ## grid 1 [2:-------------------------]| @@ -1283,7 +1304,7 @@ describe('cmdheight=0', function() [2:-------------------------]| [2:-------------------------]| ## grid 2 - ^ | + ^p | {1:~ }| {1:~ }| {1:~ }| @@ -1302,7 +1323,7 @@ describe('cmdheight=0', function() [2:-------------------------]| [3:-------------------------]| ## grid 2 - | + {6:p} | {1:~ }| {1:~ }| {1:~ }| @@ -1427,7 +1448,21 @@ describe('cmdheight=0', function() | ]]) command('set cmdheight=0') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + ]]} command('resize -1') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {2:[No Name] }| + | + ]]} command('resize +1') screen:expect([[ ^ | diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index daa4b4bdb3..f378c50792 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -1237,6 +1237,25 @@ describe('extmark decorations', function() | ]]} + meths.buf_set_extmark(0, ns, 4, 50, { virt_text={{'EOL', 'NonText'}} }) + screen:expect{grid=[[ + for _,item in ipairs(items) do | + local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}| + if | + hl_id_cell ~= nil then {4:Much} {4:MUCH}| + --^ -- -- -- -- -- -- --{4:Error}- -- hl_i{4:ERROR}| + l_id_cell {1:EOL} | + end | + for _ = 1, (count or 1) do | + local cell = line[colpos] | + {1:-} cell.text = text {1:-}| + cell.hl_id = hl_id | + colpos = colpos+1 | + end | + end | + | + ]]} + feed '.' screen:expect{grid=[[ for _,item in ipairs(items) do | @@ -1244,7 +1263,7 @@ describe('extmark decorations', function() if | hl_id_cell ~= nil then {4:Much} {4:MUCH}| --^ -- -- -- -- -- -- -- -- -- -- -- hl_id | - = hl_id_cell {4:Error} {4:ERROR}| + = hl_id_cell {1:EOL} {4:Error} {4:ERROR}| end | for _ = 1, (count or 1) do | local cell = line[colpos] | @@ -1264,7 +1283,7 @@ describe('extmark decorations', function() {2: 3 } if | {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}| {2: 5 } --^ -- -- -- -- -- -- -- -- -- -- -- hl| - {2: }_id = hl_id_cell {4:Error} {4:ERROR}| + {2: }_id = hl_id_cell {1:EOL} {4:Error} {4:ERROR}| {2: 6 } end | {2: 7 } for _ = 1, (count or 1) do | {2: 8 } local cell = line[colpos] | @@ -1283,7 +1302,7 @@ describe('extmark decorations', function() {2: 3 } if | {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}| {2: 5 } --^ -- -- -- -- -- -- -- -- -- -- -- hl| - _id = hl_id_cell {4:Error} {4:ERROR}| + _id = hl_id_cell {1:EOL} {4:Error} {4:ERROR}| {2: 6 } end | {2: 7 } for _ = 1, (count or 1) do | {2: 8 } local cell = line[colpos] | @@ -1445,6 +1464,8 @@ describe('extmark decorations', function() end) it('can have virtual text which combines foreground and background groups', function() + screen:try_resize(20, 5) + screen:set_default_attr_ids { [1] = {bold=true, foreground=Screen.colors.Blue}; [2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')}; @@ -1462,30 +1483,24 @@ describe('extmark decorations', function() hi VeryBold gui=bold ]] - meths.buf_set_extmark(0, ns, 0, 0, { virt_text={ + insert('##') + local vt = { {'a', {'BgOne', 'FgEin'}}; {'b', {'BgOne', 'FgZwei'}}; {'c', {'BgTwo', 'FgEin'}}; {'d', {'BgTwo', 'FgZwei'}}; {'X', {'BgTwo', 'FgZwei', 'VeryBold'}}; - }}) - - screen:expect{grid=[[ - ^ {2:a}{3:b}{4:c}{5:d}{6:X} | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | + } + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'eol' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'right_align' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'inline' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_lines = { vt, vt } }) + screen:expect{grid=[[ + {2:a}{3:b}{4:c}{5:d}{6:X}#^# {2:a}{3:b}{4:c}{5:d}{6:X} {2:a}{3:b}{4:c}{5:d}{6:X}| + {2:a}{3:b}{4:c}{5:d}{6:X} | + {2:a}{3:b}{4:c}{5:d}{6:X} | + {1:~ }| + | ]]} end) @@ -1891,6 +1906,84 @@ describe('extmark decorations', function() | ]]} end) + + it('virtual text works with rightleft', function() + screen:try_resize(50, 3) + insert('abcdefghijklmn') + feed('0') + command('set rightleft') + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'EOL', 'Underlined'}}}) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'right_align', 'Underlined'}}, virt_text_pos = 'right_align' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'win_col', 'Underlined'}}, virt_text_win_col = 20 }) + meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'overlayed', 'Underlined'}}, virt_text_pos = 'overlay' }) + screen:expect{grid=[[ + {28:ngila_thgir} {28:loc_niw} {28:LOE} nml{28:deyalrevo}b^a| + {1: ~}| + | + ]]} + + insert(('#'):rep(32)) + feed('0') + screen:expect{grid=[[ + {28:ngila_tdeyalrevo}ba#####{28:loc_niw}###################^#| + {1: ~}| + | + ]]} + + insert(('#'):rep(16)) + feed('0') + screen:expect{grid=[[ + {28:ngila_thgir}############{28:loc_niw}###################^#| + {28:LOE} nml{28:deyalrevo}| + | + ]]} + + insert('###') + feed('0') + screen:expect{grid=[[ + #################################################^#| + {28:ngila_thgir} {28:loc_niw} {28:LOE} nml{28:deyalrevo}ba#| + | + ]]} + + command('set number') + screen:expect{grid=[[ + #############################################^#{2: 1 }| + {28:ngila_thgir} {28:loc_niw} nml{28:deyalrevo}ba#####{2: }| + | + ]]} + + command('set cpoptions+=n') + screen:expect{grid=[[ + #############################################^#{2: 1 }| + {28:ngila_thgir} {28:loc_niw} nml{28:deyalrevo}ba#####| + | + ]]} + end) + + it('works with double width char and rightleft', function() + screen:try_resize(50, 3) + insert('abcdefghij口klmnop') + feed('0') + command('set rightleft') + screen:expect{grid=[[ + ponmlk口jihgfedcb^a| + {1: ~}| + | + ]]} + meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'overlayed', 'Underlined'}}, virt_text_pos = 'overlay' }) + screen:expect{grid=[[ + ponmlk {28:deyalrevo}b^a| + {1: ~}| + | + ]]} + meths.buf_set_extmark(0, ns, 0, 15, { virt_text = {{'古', 'Underlined'}}, virt_text_pos = 'overlay' }) + screen:expect{grid=[[ + po{28:古}lk {28:deyalrevo}b^a| + {1: ~}| + | + ]]} + end) end) describe('decorations: inline virtual text', function() @@ -4226,6 +4319,7 @@ if (h->n_buckets < new_n_buckets) { // expand end) it('does not show twice if end_row or end_col is specified #18622', function() + screen:try_resize(50, 8) insert([[ aaa bbb @@ -4241,10 +4335,28 @@ if (h->n_buckets < new_n_buckets) { // expand dd^d | {1:VIRT LINE 2} | {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + | + ]]} + end) + + it('works with rightleft', function() + screen:try_resize(50, 8) + insert([[ + aaa + bbb + ccc + ddd]]) + command('set number rightleft') + meths.buf_set_extmark(0, ns, 0, 0, {virt_lines = {{{'VIRT LINE 1', 'NonText'}}}, virt_lines_leftcol = true}) + meths.buf_set_extmark(0, ns, 3, 0, {virt_lines = {{{'VIRT LINE 2', 'NonText'}}}}) + screen:expect{grid=[[ + aaa{9: 1 }| + {1:1 ENIL TRIV}| + bbb{9: 2 }| + ccc{9: 3 }| + ^ddd{9: 4 }| + {1:2 ENIL TRIV}{9: }| + {1: ~}| | ]]} end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index e37b3ccb5f..6db9e7af3e 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -103,6 +103,29 @@ describe('float window', function() assert_alive() end) + it('open with WinNew autocmd', function() + local res = exec_lua([[ + local triggerd = false + local buf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_create_autocmd('WinNew', { + callback = function(opt) + if opt.buf == buf then + triggerd = true + end + end + }) + local opts = { + relative = "win", + row = 0, col = 0, + width = 1, height = 1, + noautocmd = false, + } + vim.api.nvim_open_win(buf, true, opts) + return triggerd + ]]) + eq(true, res) + end) + it('opened with correct height', function() local height = exec_lua([[ vim.go.winheight = 20 @@ -499,6 +522,27 @@ describe('float window', function() eq(5, meths.get_option_value('scroll', {win=float_win.id})) end) + it(':unhide works when there are floating windows', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 5, height = 5} + local w0 = curwin() + meths.open_win(0, false, float_opts) + meths.open_win(0, false, float_opts) + eq(3, #meths.list_wins()) + command('unhide') + eq({ w0 }, meths.list_wins()) + end) + + it(':all works when there are floating windows', function() + command('args Xa.txt') + local float_opts = {relative = 'editor', row = 1, col = 1, width = 5, height = 5} + local w0 = curwin() + meths.open_win(0, false, float_opts) + meths.open_win(0, false, float_opts) + eq(3, #meths.list_wins()) + command('all') + eq({ w0 }, meths.list_wins()) + end) + describe('with only one tabpage,', function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local old_buf, old_win @@ -798,7 +842,7 @@ describe('float window', function() [4] = {bold = true, reverse = true}, [5] = {reverse = true}, [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, - [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [7] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, [9] = {background = Screen.colors.LightGrey, underline = true}, [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, @@ -939,6 +983,71 @@ describe('float window', function() end end) + it('window position fixed', function() + command('rightbelow 20vsplit') + local buf = meths.create_buf(false,false) + local win = meths.open_win(buf, false, { + relative='win', width=15, height=2, row=2, col=10, anchor='NW', fixed=true}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------]{5:│}[4:--------------------]| + [2:-------------------]{5:│}[4:--------------------]| + [2:-------------------]{5:│}[4:--------------------]| + [2:-------------------]{5:│}[4:--------------------]| + [2:-------------------]{5:│}[4:--------------------]| + {5:[No Name] }{4:[No Name] }| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 5 + {1: }| + {2:~ }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 4, 2, 10, true, 50}; + }} + else + screen:expect([[ + {5:│}^ | + {0:~ }{5:│}{0:~ }| + {0:~ }{5:│}{0:~ }{1: }| + {0:~ }{5:│}{0:~ }{2:~ }| + {0:~ }{5:│}{0:~ }| + {5:[No Name] }{4:[No Name] }| + | + ]]) + end + + meths.win_set_config(win, {fixed=false}) + + if multigrid then + screen:expect_unchanged() + else + screen:expect([[ + {5:│}^ | + {0:~ }{5:│}{0:~ }| + {0:~ }{5:│}{0:~ }{1: }| + {0:~ }{5:│}{0:~ }{2:~ }| + {0:~ }{5:│}{0:~ }| + {5:[No Name] }{4:[No Name] }| + | + ]]) + end + end) + it('draws correctly with redrawdebug=compositor', function() -- NB: we do not test that it produces the "correct" debug info -- (as it is intermediate only, and is allowed to change by internal @@ -2424,7 +2533,12 @@ describe('float window', function() ]]} end - meths.win_set_config(win, {title= { {"🦄"},{"BB"}},title_pos="right",footer= { {"🦄"},{"BB"}},footer_pos="right"}) + command('hi B0 guibg=Red guifg=Black') + command('hi B1 guifg=White') + meths.win_set_config(win, { + title = {{"🦄"}, {"BB", {"B0", "B1"}}}, title_pos = "right", + footer= {{"🦄"}, {"BB", {"B0", "B1"}}}, footer_pos = "right", + }) if multigrid then screen:expect{grid=[[ ## grid 1 @@ -2445,10 +2559,10 @@ describe('float window', function() ## grid 3 | ## grid 4 - {5:╔═════}🦄BB{5:╗}| + {5:╔═════}🦄{7:BB}{5:╗}| {5:║}{1: halloj! }{5:║}| {5:║}{1: BORDAA }{5:║}| - {5:╚═════}🦄BB{5:╝}| + {5:╚═════}🦄{7:BB}{5:╝}| ]], float_pos={ [4] = { { id = 1001 }, "NW", 1, 2, 5, true } }, win_viewport={ @@ -2459,10 +2573,10 @@ describe('float window', function() screen:expect{grid=[[ ^ | {0:~ }| - {0:~ }{5:╔═════}🦄BB{5:╗}{0: }| + {0:~ }{5:╔═════}🦄{7:BB}{5:╗}{0: }| {0:~ }{5:║}{1: halloj! }{5:║}{0: }| {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| - {0:~ }{5:╚═════}🦄BB{5:╝}{0: }| + {0:~ }{5:╚═════}🦄{7:BB}{5:╝}{0: }| | ]]} end @@ -3119,6 +3233,154 @@ describe('float window', function() end end) + describe('no crash when rearranging windows', function() + local function test_rearrange_windows(cmd) + command('set laststatus=2') + screen:try_resize(40, 13) + + command('args X1 X2 X3 X4 X5 X6') + command('sargument 2') + command('sargument 3') + local w3 = curwin() + command('sargument 4') + local w4 = curwin() + command('sargument 5') + command('sargument 6') + + local float_opts = { relative = 'editor', row = 6, col = 0, width = 40, height = 1 } + meths.win_set_config(w3, float_opts) + meths.win_set_config(w4, float_opts) + command('wincmd =') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [8:----------------------------------------]| + [8:----------------------------------------]| + {4:X6 }| + [7:----------------------------------------]| + [7:----------------------------------------]| + {5:X5 }| + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:X2 }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:X1 }| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + ## grid 3 + | + ## grid 4 + | + {0:~ }| + ## grid 5 + {1: }| + ## grid 6 + {1: }| + ## grid 7 + | + {0:~ }| + ## grid 8 + ^ | + {0:~ }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 6, 0, true, 50}; + [6] = {{id = 1003}, "NW", 1, 6, 0, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [6] = {win = {id = 1003}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [7] = {win = {id = 1004}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [8] = {win = {id = 1005}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {4:X6 }| + | + {0:~ }| + {5:X5 }| + {1: }| + {0:~ }| + {5:X2 }| + | + {0:~ }| + {5:X1 }| + | + ]]} + end + + command(cmd) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + {4:X1 }| + [4:----------------------------------------]| + {5:X2 }| + [9:----------------------------------------]| + {5:X3 }| + [10:----------------------------------------]| + {5:X4 }| + [7:----------------------------------------]| + {5:X5 }| + [8:----------------------------------------]| + {5:X6 }| + [3:----------------------------------------]| + ## grid 2 + ^ | + ## grid 3 + | + ## grid 4 + | + ## grid 7 + | + ## grid 8 + | + ## grid 9 + | + ## grid 10 + | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [7] = {win = {id = 1004}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [8] = {win = {id = 1005}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [9] = {win = {id = 1006}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [10] = {win = {id = 1007}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {4:X1 }| + | + {5:X2 }| + | + {5:X3 }| + | + {5:X4 }| + | + {5:X5 }| + | + {5:X6 }| + | + ]]} + end + end + + it('using :unhide', function() + test_rearrange_windows('unhide') + end) + + it('using :all', function() + test_rearrange_windows('all') + end) + end) + it('API has proper error messages', function() local buf = meths.create_buf(false,false) eq("Invalid key: 'bork'", @@ -6199,7 +6461,7 @@ describe('float window', function() end if multigrid then - meths.input_mouse('left', 'press', '', 1, 0, 0) + meths.input_mouse('left', 'press', '', 2, 0, 0) screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| @@ -6278,7 +6540,7 @@ describe('float window', function() end if multigrid then - meths.input_mouse('left', 'press', '', 1, 0, 0) + meths.input_mouse('left', 'press', '', 2, 0, 0) screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| @@ -8676,6 +8938,186 @@ describe('float window', function() end end) + it('left click sets correct curswant in float window with border', function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'', '', ''}) + meths.open_win(buf, false, {relative='editor', width=20, height=3, row=0, col=5, border='single'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{1: }{5:│}| + {5:│}{1: }{5:│}| + {5:│}{1: }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + ^ {5:┌────────────────────┐} | + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 1) + else + meths.input_mouse('left', 'press', '', 0, 3, 6) + end + eq({0, 3, 1, 0, 1}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 2) + else + meths.input_mouse('left', 'press', '', 0, 3, 7) + end + eq({0, 3, 1, 0, 2}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 10) + else + meths.input_mouse('left', 'press', '', 0, 3, 15) + end + eq({0, 3, 1, 0, 10}, funcs.getcurpos()) + + command('setlocal foldcolumn=1') + feed('zfkgg') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{19: }{1:^ }{5:│}| + {5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}| + {5:│}{2:~ }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }| + {0:~ }{5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 1) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{19: }{1:^ }{5:│}| + {5:│}{19:-}{1: }{5:│}| + {5:│}{19:│}{1: }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + meths.input_mouse('left', 'press', '', 0, 2, 6) + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }| + {0:~ }{5:│}{19:-}{1: }{5:│}{0: }| + {0:~ }{5:│}{19:│}{1: }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 2) + else + meths.input_mouse('left', 'press', '', 0, 2, 7) + end + eq({0, 2, 1, 0, 1}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 3) + else + meths.input_mouse('left', 'press', '', 0, 2, 8) + end + eq({0, 2, 1, 0, 2}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 11) + else + meths.input_mouse('left', 'press', '', 0, 2, 16) + end + eq({0, 2, 1, 0, 10}, funcs.getcurpos()) + end) + it("'winblend' option", function() screen:try_resize(50,9) screen:set_default_attr_ids({ diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 46de6b114e..f42682ba69 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -33,7 +33,7 @@ describe("folded lines", function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {reverse = true}, [3] = {bold = true, reverse = true}, - [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, [6] = {background = Screen.colors.Yellow}, [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, @@ -423,6 +423,119 @@ describe("folded lines", function() :set norightleft | ]]) end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:▸ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set norightleft | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + {7:▸ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set norightleft | + ]]) + end + + -- Add a winbar to avoid double-clicks + command('setlocal winbar=!!!!!!') + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {11:!!!!!! }| + {7:▾▸}{5:^+--- 5 lines: aa··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :set norightleft | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {11:!!!!!! }| + {7:▾▸}{5:^+--- 5 lines: aa··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :set norightleft | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 1) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {11:!!!!!! }| + {7:▾▾}^aa | + {7:││}bb | + {7:││}cc | + {7:││}dd | + {7:││}ee | + {7:│ }ff | + ## grid 3 + :set norightleft | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 1) + screen:expect([[ + {11:!!!!!! }| + {7:▾▾}^aa | + {7:││}bb | + {7:││}cc | + {7:││}dd | + {7:││}ee | + {7:│ }ff | + :set norightleft | + ]]) + end end) it("works with split", function() @@ -2825,16 +2938,21 @@ describe("folded lines", function() screen:try_resize(30, 7) insert(content1) command("hi! CursorLine guibg=NONE guifg=Red gui=NONE") + command('hi F0 guibg=Red guifg=Black') + command('hi F1 guifg=White') meths.set_option_value('cursorline', true, {}) meths.set_option_value('foldcolumn', '4', {}) - meths.set_option_value('foldtext', - '[[v:folddashes], ["\t", "Search"], [getline(v:foldstart), "NonText"]]', {}) + meths.set_option_value('foldtext', '[' + .. '["▶", ["F0", "F1"]], ' + .. '[v:folddashes], ' + .. '["\t", "Search"], ' + .. '[getline(v:foldstart), "NonText"]]', {}) command('3,4fold') command('5,6fold') command('2,6fold') if multigrid then - screen:expect{grid=[[ + screen:expect([[ ## grid 1 [2:------------------------------]| [2:------------------------------]| @@ -2845,18 +2963,18 @@ describe("folded lines", function() [3:------------------------------]| ## grid 2 {7: }This is a | - {7:+ }{13:^-}{17: }{18:valid English}{13:·····}| + {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}| {1:~ }| {1:~ }| {1:~ }| {1:~ }| ## grid 3 | - ]]} + ]]) else screen:expect([[ {7: }This is a | - {7:+ }{13:^-}{17: }{18:valid English}{13:·····}| + {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}| {1:~ }| {1:~ }| {1:~ }| @@ -2864,11 +2982,11 @@ describe("folded lines", function() | ]]) end - eq('-\tvalid English', funcs.foldtextresult(2)) + eq('▶-\tvalid English', funcs.foldtextresult(2)) feed('zo') if multigrid then - screen:expect{grid=[[ + screen:expect([[ ## grid 1 [2:------------------------------]| [2:------------------------------]| @@ -2880,31 +2998,31 @@ describe("folded lines", function() ## grid 2 {7: }This is a | {7:- }valid English | - {7:│+ }{5:--}{19: }{18:sentence composed }| - {7:│+ }{13:^--}{17: }{18:in his cave.}{13:······}| + {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }| + {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}| {1:~ }| {1:~ }| ## grid 3 | - ]]} + ]]) else screen:expect([[ {7: }This is a | {7:- }valid English | - {7:│+ }{5:--}{19: }{18:sentence composed }| - {7:│+ }{13:^--}{17: }{18:in his cave.}{13:······}| + {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }| + {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}| {1:~ }| {1:~ }| | ]]) end - eq('--\tsentence composed by', funcs.foldtextresult(3)) - eq('--\tin his cave.', funcs.foldtextresult(5)) + eq('▶--\tsentence composed by', funcs.foldtextresult(3)) + eq('▶--\tin his cave.', funcs.foldtextresult(5)) command('hi! Visual guibg=Red') feed('V2k') if multigrid then - screen:expect{grid=[[ + screen:expect([[ ## grid 1 [2:------------------------------]| [2:------------------------------]| @@ -2916,24 +3034,57 @@ describe("folded lines", function() ## grid 2 {7: }This is a | {7:- }^v{14:alid English} | - {7:│+ }{15:--}{19: }{20:sentence composed }| - {7:│+ }{15:--}{19: }{20:in his cave.}{15:······}| + {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }| + {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}| {1:~ }| {1:~ }| ## grid 3 {11:-- VISUAL LINE --} | - ]]} + ]]) else screen:expect([[ {7: }This is a | {7:- }^v{14:alid English} | - {7:│+ }{15:--}{19: }{20:sentence composed }| - {7:│+ }{15:--}{19: }{20:in his cave.}{15:······}| + {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }| + {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}| {1:~ }| {1:~ }| {11:-- VISUAL LINE --} | ]]) end + + meths.set_option_value('rightleft', true, {}) + if multigrid then + screen:expect([[ + ## grid 1 + [2:------------------------------]| + [2:------------------------------]| + [2:------------------------------]| + [2:------------------------------]| + [2:------------------------------]| + [2:------------------------------]| + [3:------------------------------]| + ## grid 2 + a si sihT{7: }| + {14:hsilgnE dila}^v{7: -}| + {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}| + {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}| + {1: ~}| + {1: ~}| + ## grid 3 + {11:-- VISUAL LINE --} | + ]]) + else + screen:expect([[ + a si sihT{7: }| + {14:hsilgnE dila}^v{7: -}| + {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}| + {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}| + {1: ~}| + {1: ~}| + {11:-- VISUAL LINE --} | + ]]) + end end) end diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index c68f4cf34c..931e1f9985 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -8,6 +8,7 @@ local feed_command, eq = helpers.feed_command, helpers.eq local curbufmeths = helpers.curbufmeths local funcs = helpers.funcs local meths = helpers.meths +local exec_lua = helpers.exec_lua describe('colorscheme compatibility', function() before_each(function() @@ -2641,4 +2642,17 @@ describe('highlight namespaces', function() | ]]} end) + + it('winhl does not accept invalid value #24586', function() + local res = exec_lua([[ + local curwin = vim.api.nvim_get_current_win() + vim.api.nvim_command("set winhl=Normal:Visual") + local _, msg = pcall(vim.api.nvim_command,"set winhl='Normal:Wrong'") + return { msg, vim.wo[curwin].winhl } + ]]) + eq({ + "Vim(set):E5248: Invalid character in group name", + "Normal:Visual", + },res) + end) end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index a8d01cfbf1..fd24174f74 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -1676,7 +1676,7 @@ describe('ui/mouse/input', function() end) - it('getmousepos works correctly', function() + it('getmousepos() works correctly', function() local winwidth = meths.get_option_value('winwidth', {}) -- Set winwidth=1 so that window sizes don't change. meths.set_option_value('winwidth', 1, {}) @@ -1771,7 +1771,7 @@ describe('ui/mouse/input', function() -- Test that mouse position values are properly set for ordinary windows. -- Set the float to be unfocusable instead of closing, to additionally test - -- that getmousepos does not consider unfocusable floats. (see discussion + -- that getmousepos() does not consider unfocusable floats. (see discussion -- in PR #14937 for details). opts.focusable = false meths.win_set_config(float, opts) diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua index 5cecd423d7..417c7b797c 100644 --- a/test/functional/ui/multibyte_spec.lua +++ b/test/functional/ui/multibyte_spec.lua @@ -160,6 +160,22 @@ describe("multibyte rendering", function() {1:~ }| | ]]} + + -- nvim will reset the zalgo text^W^W glyph cache if it gets too full. + -- this should be exceedingly rare, but fake it to make sure it works + meths._invalidate_glyph_cache() + screen:expect{grid=[[ + ^L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ | + ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ | + U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ | + u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ | + D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ | + ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ | + š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ | + o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚ | + {1:~ }| + | + ]], reset=true} end) end) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 1778c8218b..6f4082beda 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -2307,6 +2307,7 @@ describe('ext_multigrid', function() {1:~ }| ]]} + -- XXX: mouse_check_grid() doesn't work properly when clicking on grid 1 meths.input_mouse('left', 'press', '', 1, 6, 20) -- TODO(bfredl): "batching" input_mouse is formally not supported yet. -- Normally it should work fine in async context when nvim is not blocked, diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 742976cbe2..a7830abe8f 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -475,7 +475,7 @@ describe('statuscolumn', function() aaaaaaaaa | {1:virtual-2 15}virt_line1 | {1:virtual-2 15}virt_line2 | - {1:buffer 0 16}{5:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {4:buffer 0 16}{5:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {5:aaaaaaaaa }| {1:virtual-1 16}END | {0:~ }| diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 7967fb6865..182e0cdadf 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -799,3 +799,21 @@ it("shows correct ruler in cmdline with no statusline", function() 3longlonglong | ]] end) + +it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + clear() + local screen = Screen.new(53, 4) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect{grid=[[ + ^ │ | + {1:~ }│{1:~ }| + [No Name] [No Name] | + | + ]]} +end) diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index b3f2c1bfeb..ab0ffccd4d 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -153,11 +153,6 @@ end) describe("uncaught exception", function() before_each(clear) - after_each(function() - os.remove('throw1.vim') - os.remove('throw2.vim') - os.remove('throw3.vim') - end) it('is not forgotten #13490', function() command('autocmd BufWinEnter * throw "i am error"') @@ -173,10 +168,45 @@ describe("uncaught exception", function() let result ..= 'X' ]]):format(i, i)) end + finally(function() + for i = 1, 3 do + os.remove('throw' .. i .. '.vim') + end + end) + command('set runtimepath+=. | let result = ""') eq('throw1', exc_exec('try | runtime! throw*.vim | endtry')) eq('123', eval('result')) end) + + it('multiline exception remains multiline #25350', function() + local screen = Screen.new(80, 11) + screen:set_default_attr_ids({ + [1] = {bold = true, reverse = true}; -- MsgSeparator + [2] = {foreground = Screen.colors.White, background = Screen.colors.Red}; -- ErrorMsg + [3] = {bold = true, foreground = Screen.colors.SeaGreen}; -- MoreMsg + }) + screen:attach() + exec_lua([[ + function _G.Oops() + error("oops") + end + ]]) + feed(':try\rlua _G.Oops()\rendtry\r') + screen:expect{grid=[[ + {1: }| + :try | + : lua _G.Oops() | + : endtry | + {2:Error detected while processing :} | + {2:E5108: Error executing lua [string "<nvim>"]:2: oops} | + {2:stack traceback:} | + {2: [C]: in function 'error'} | + {2: [string "<nvim>"]:2: in function 'Oops'} | + {2: [string ":lua"]:1: in main chunk} | + {3:Press ENTER or type command to continue}^ | + ]]} + end) end) describe('listing functions using :function', function() diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 94fbf2bef1..4e8859a33e 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -4,7 +4,6 @@ if exists('s:did_load') set commentstring=/*%s*/ set complete=.,w,b,u,t,i set define=^\\s*#\\s*define - set directory& set directory^=. set display= set fillchars=vert:\|,foldsep:\|,fold:- @@ -21,13 +20,10 @@ if exists('s:did_load') set shortmess=filnxtToOS set sidescroll=0 set tags=./tags,tags - set undodir& set undodir^=. set wildoptions= set startofline - set sessionoptions& set sessionoptions+=options - set viewoptions& set viewoptions+=options set switchbuf= if g:testname !~ 'test_mapping.vim$' @@ -59,7 +55,6 @@ endfunc " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' - " Make sure 'runtimepath' and 'packpath' does not include $HOME. set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after let &packpath = &rtp diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 0afa3417ec..b3238e8214 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -4,6 +4,7 @@ source shared.vim source check.vim source term_util.vim source screendump.vim +source vim9.vim source load.vim func s:cleanup_buffers() abort @@ -3432,6 +3433,31 @@ func Test_autocmd_vimgrep() augroup END endfunc +func Test_closing_autocmd_window() + let lines =<< trim END + edit Xa.txt + tabnew Xb.txt + autocmd BufEnter Xa.txt unhide 1 + doautoall BufEnter + END + call CheckScriptFailure(lines, 'E814:') + au! BufEnter + bwipe Xa.txt + bwipe Xb.txt +endfunc + +func Test_switch_window_in_autocmd_window() + edit Xa.txt + tabnew Xb.txt + autocmd BufEnter Xa.txt wincmd w + doautoall BufEnter + au! BufEnter + bwipe Xa.txt + call assert_false(bufexists('Xa.txt')) + bwipe Xb.txt + call assert_false(bufexists('Xb.txt')) +endfunc + func Test_bufwipeout_changes_window() " This should not crash, but we don't have any expectations about what " happens, changing window in BufWipeout has unpredictable results. @@ -3645,7 +3671,7 @@ endfunc func SetupVimTest_shm() let g:bwe = [] let g:brp = [] - set shortmess+=F + set shortmess-=l messages clear let dirname='XVimTestSHM' diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim index ec7d143030..0b22bafabb 100644 --- a/test/old/testdir/test_compiler.vim +++ b/test/old/testdir/test_compiler.vim @@ -7,10 +7,8 @@ func Test_compiler() CheckExecutable perl CheckFeature quickfix - " $LANG changes the output of Perl. - if $LANG != '' - unlet $LANG - endif + let save_LC_ALL = $LC_ALL + let $LC_ALL= "C" " %:S does not work properly with 'shellslash' set let save_shellslash = &shellslash @@ -40,12 +38,13 @@ func Test_compiler() let &shellslash = save_shellslash call delete('Xfoo.pl') bw! + let $LC_ALL = save_LC_ALL endfunc func GetCompilerNames() return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) - \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) - \ ->sort() + \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) + \ ->sort() endfunc func Test_compiler_without_arg() diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index b2b6ad80bb..e5d7085d71 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -97,6 +97,7 @@ func s:GetFilenameChecks() abort \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'], \ 'astro': ['file.astro'], \ 'atlas': ['file.atl', 'file.as'], + \ 'authzed': ['file.zed'], \ 'autohotkey': ['file.ahk'], \ 'autoit': ['file.au3'], \ 'automake': ['GNUmakefile.am', 'makefile.am', 'Makefile.am'], @@ -362,6 +363,7 @@ func s:GetFilenameChecks() abort \ 'lilo': ['lilo.conf', 'lilo.conf-file'], \ 'lilypond': ['file.ly', 'file.ily'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], + \ 'liquidsoap': ['file.liq'], \ 'liquid': ['file.liquid'], \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], diff --git a/test/old/testdir/test_jumplist.vim b/test/old/testdir/test_jumplist.vim index 91ad940e18..b4dcdad9d6 100644 --- a/test/old/testdir/test_jumplist.vim +++ b/test/old/testdir/test_jumplist.vim @@ -104,4 +104,70 @@ d bwipe! endfunc +" Test for 'jumpoptions' +func Test_jumpoptions() + new + call setline(1, range(1, 200)) + clearjumps + set jumpoptions=stack + + " Jump around to add some locations to the jump list. + normal 10G + normal 20G + normal 30G + normal 40G + normal 50G + let bnr = bufnr() + + " discards the tail when navigating from the middle + exe "normal \<C-O>\<C-O>" + call assert_equal([ + \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 40, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0} + \ ], 3], getjumplist()) + + " new jump location is added immediately after the last one + normal 90G + call assert_equal([ + \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ ], 4], getjumplist()) + + " does not add the same location twice adjacently + normal 60G + normal 60G + call assert_equal([ + \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + "\ Nvim: avoids useless/phantom jumps + "\ {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + "\ ], 6], getjumplist()) + \ ], 5], getjumplist()) + + " does add the same location twice non adjacently + normal 10G + normal 20G + call assert_equal([ + \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ ], 7], getjumplist()) + + set jumpoptions& + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_listchars.vim b/test/old/testdir/test_listchars.vim index eed4d7e30c..5366f503fc 100644 --- a/test/old/testdir/test_listchars.vim +++ b/test/old/testdir/test_listchars.vim @@ -4,6 +4,36 @@ source check.vim source view_util.vim source screendump.vim +func Check_listchars(expected, end_lnum, end_scol = -1, leftcol = 0) + if a:leftcol > 0 + let save_wrap = &wrap + set nowrap + call cursor(1, 1) + exe 'normal! ' .. a:leftcol .. 'zl' + endif + + redraw! + for i in range(1, a:end_lnum) + if a:leftcol > 0 + let col = virtcol2col(0, i, a:leftcol) + let col += getline(i)->strpart(col - 1, 1, v:true)->len() + call cursor(i, col) + redraw + call assert_equal(a:leftcol, winsaveview().leftcol) + else + call cursor(i, 1) + end + + let end_scol = a:end_scol < 0 ? '$'->virtcol() - a:leftcol : a:end_scol + call assert_equal([a:expected[i - 1]->strcharpart(a:leftcol)], + \ ScreenLines(i, end_scol)) + endfor + + if a:leftcol > 0 + let &wrap = save_wrap + endif +endfunc + func Test_listchars() enew! set ff=unix @@ -24,11 +54,8 @@ func Test_listchars() \ 'dd........ee<<>-$', \ '<$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, '$'->virtcol())) - endfor + call Check_listchars(expected, 5) + call Check_listchars(expected, 4, -1, 5) set listchars-=trail:< let expected = [ @@ -38,11 +65,8 @@ func Test_listchars() \ 'dd........ee..>-$', \ '.$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor + call Check_listchars(expected, 5) + call Check_listchars(expected, 4, -1, 5) " tab with 3rd character. set listchars-=tab:>- @@ -54,11 +78,8 @@ func Test_listchars() \ 'dd........ee--<>$', \ '-$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor + call Check_listchars(expected, 5) + call Check_listchars(expected, 4, -1, 5) " tab with 3rd character and linebreak set set listchars-=tab:<=> @@ -71,11 +92,7 @@ func Test_listchars() \ 'dd........ee--<>$', \ '-$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor + call Check_listchars(expected, 5) set nolinebreak set listchars-=tab:<·> set listchars+=tab:<=> @@ -88,11 +105,8 @@ func Test_listchars() \ 'dd........ee..<>$', \ '.$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor + call Check_listchars(expected, 5) + call Check_listchars(expected, 4, -1, 5) set listchars-=tab:<=> set listchars+=tab:>- @@ -110,7 +124,8 @@ func Test_listchars() \ '..fff>--<<$', \ '>-------gg>-----$', \ '.....h>-$', - \ 'iii<<<<><<$', '$'], l) + \ 'iii<<<<><<$', + \ '$'], l) " Test lead and trail normal ggdG @@ -132,14 +147,10 @@ func Test_listchars() \ 'h<<<<<<<<<<<$', \ '<<<<<<<<<<<<$', \ '>>>>0xx0<<<<$', - \ '$' + \ '$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " Test multispace @@ -162,14 +173,10 @@ func Test_listchars() \ ' hyYzZyYzZyY$', \ 'yYzZyYzZyYj $', \ 'yYzZ0yY0yYzZ$', - \ '$' + \ '$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " Test leadmultispace + multispace @@ -192,15 +199,14 @@ func Test_listchars() \ ' hyYzZyYzZyY$', \ '.-+*.-+*.-j $', \ '.-+*0yY0yYzZ$', - \ '$' + \ '$' \ ] - redraw! call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars) - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 1) + call Check_listchars(expected, 5, -1, 2) + call Check_listchars(expected, 5, -1, 3) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " Test leadmultispace without multispace @@ -223,16 +229,14 @@ func Test_listchars() \ '+h>>>>>>>>>>$', \ '.-+*.-+*.-j>$', \ '.-+*0++0>>>>$', - \ '$', + \ '$' \ ] - - redraw! call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars) - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 1) + call Check_listchars(expected, 5, -1, 2) + call Check_listchars(expected, 5, -1, 3) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " Test leadmultispace only @@ -255,14 +259,10 @@ func Test_listchars() \ ' h ', \ '.-+*.-+*.-j ', \ '.-+*0 0 ', - \ ' ', + \ ' ' \ ] - redraw! call assert_equal('leadmultispace:.-+*', &listchars) - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, 12)) - endfor + call Check_listchars(expected, 5, 12) call assert_equal(expected, split(execute("%list"), "\n")) " Test leadmultispace and lead and space @@ -286,14 +286,14 @@ func Test_listchars() \ '<h----------$', \ '.-+*.-+*.-j-$', \ '.-+*0--0----$', - \ '$', + \ '$' \ ] - redraw! call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars) - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 1) + call Check_listchars(expected, 5, -1, 2) + call Check_listchars(expected, 5, -1, 3) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " the last occurrence of 'multispace:' is used @@ -307,15 +307,11 @@ func Test_listchars() \ 'xhXyYXyYXyYX$', \ 'XyYXyYXyYXjx$', \ 'XyYX0Xy0XyYX$', - \ '$' + \ '$' \ ] - redraw! call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars) - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) set listchars+=lead:>,trail:< @@ -326,14 +322,10 @@ func Test_listchars() \ '>h<<<<<<<<<<$', \ '>>>>>>>>>>j<$', \ '>>>>0Xy0<<<<$', - \ '$' + \ '$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " removing 'multispace:' @@ -346,14 +338,10 @@ func Test_listchars() \ '>h<<<<<<<<<<$', \ '>>>>>>>>>>j<$', \ '>>>>0xx0<<<<$', - \ '$' + \ '$' \ ] - redraw! - for i in range(1, 5) - call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) - endfor - + call Check_listchars(expected, 6) + call Check_listchars(expected, 5, -1, 6) call assert_equal(expected, split(execute("%list"), "\n")) " test nbsp @@ -365,15 +353,10 @@ func Test_listchars() call append(0, [ ">" .. nbsp .. "<" ]) let expected = '>X< ' - - redraw! - call cursor(1, 1) - call assert_equal([expected], ScreenLines(1, virtcol('$'))) + call Check_listchars([expected], 1) set listchars=nbsp:X - redraw! - call cursor(1, 1) - call assert_equal([expected], ScreenLines(1, virtcol('$'))) + call Check_listchars([expected], 1) " test extends normal ggdG @@ -383,16 +366,11 @@ func Test_listchars() call append(0, [ repeat('A', &columns + 1) ]) let expected = repeat('A', &columns) - - redraw! - call cursor(1, 1) - call assert_equal([expected], ScreenLines(1, &columns)) + call Check_listchars([expected], 1, &columns) set list let expected = expected[:-2] . 'Z' - redraw! - call cursor(1, 1) - call assert_equal([expected], ScreenLines(1, &columns)) + call Check_listchars([expected], 1, &columns) enew! set listchars& ff& @@ -411,19 +389,20 @@ func Test_listchars_unicode() let nbsp = nr2char(0xa0) call append(0, [" a\tb c" .. nbsp .. "d "]) let expected = ['≡≢≣≡≢≣≡≢a←↔↔↔↔↔→b␣c≠d≡≢⇔'] - redraw! - call cursor(1, 1) - call assert_equal(expected, ScreenLines(1, virtcol('$'))) + call Check_listchars(expected, 1) + call Check_listchars(expected, 1, -1, 3) + call Check_listchars(expected, 1, -1, 13) set listchars=eol:\\u21d4,space:\\u2423,multispace:≡\\u2262\\U00002263,nbsp:\\U00002260,tab:←↔\\u2192 - redraw! - call assert_equal(expected, ScreenLines(1, virtcol('$'))) + call Check_listchars(expected, 1) + call Check_listchars(expected, 1, -1, 3) + call Check_listchars(expected, 1, -1, 13) set listchars+=lead:⇨,trail:⇦ let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨a←↔↔↔↔↔→b␣c≠d⇦⇦⇔'] - redraw! - call cursor(1, 1) - call assert_equal(expected, ScreenLines(1, virtcol('$'))) + call Check_listchars(expected, 1) + call Check_listchars(expected, 1, -1, 3) + call Check_listchars(expected, 1, -1, 13) let &encoding=oldencoding enew! @@ -506,7 +485,7 @@ func Test_listchars_composing() set list set listchars=eol:$,space:_,nbsp:= - + let nbsp1 = nr2char(0xa0) let nbsp2 = nr2char(0x202f) call append(0, [ @@ -515,9 +494,7 @@ func Test_listchars_composing() let expected = [ \ "_ \u3099^I \u309A=" .. nbsp1 .. "\u0302=" .. nbsp2 .. "\u0302$" \ ] - redraw! - call cursor(1, 1) - call assert_equal(expected, ScreenLines(1, virtcol('$'))) + call Check_listchars(expected, 1) let &encoding=oldencoding enew! set listchars& ff& @@ -533,22 +510,22 @@ endfunc func Test_listchars_window_local() %bw! set list listchars& - let l:default_listchars = &listchars " Accommodate Nvim default + let nvim_default = &listchars " Accommodate Nvim default new " set a local value for 'listchars' setlocal listchars=tab:+-,eol:# call s:CheckListCharsValue('tab:+-,eol:#') " When local value is reset, global value should be used setlocal listchars= - call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + call s:CheckListCharsValue(nvim_default) " Use 'setlocal <' to copy global value setlocal listchars=space:.,extends:> setlocal listchars< - call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + call s:CheckListCharsValue(nvim_default) " Use 'set <' to copy global value setlocal listchars=space:.,extends:> set listchars< - call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + call s:CheckListCharsValue(nvim_default) " Changing global setting should not change the local setting setlocal listchars=space:.,extends:> setglobal listchars=tab:+-,eol:# @@ -558,7 +535,7 @@ func Test_listchars_window_local() call s:CheckListCharsValue('space:.,extends:>') " clearing local value in one window should not change the other window set listchars& - call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + call s:CheckListCharsValue(nvim_default) close call s:CheckListCharsValue('space:.,extends:>') diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 3c21041899..09d3f0432f 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -4107,6 +4107,39 @@ func Test_normal_click_on_double_width_char() let &mouse = save_mouse endfunc +func Test_normal_click_on_empty_line() + let save_mouse = &mouse + set mouse=a + botright new + call setline(1, ['', '', '']) + let row = win_screenpos(0)[0] + 2 + 20vsplit + redraw + + call Ntest_setmouse(row, 1) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 1], getcurpos()) + call Ntest_setmouse(row, 2) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 2], getcurpos()) + call Ntest_setmouse(row, 10) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 10], getcurpos()) + + call Ntest_setmouse(row, 21 + 1) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 1], getcurpos()) + call Ntest_setmouse(row, 21 + 2) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 2], getcurpos()) + call Ntest_setmouse(row, 21 + 10) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 10], getcurpos()) + + bwipe! + let &mouse = save_mouse +endfunc + func Test_normal33_g_cmd_nonblank() " Test that g<End> goes to the last non-blank char and g$ to the last " visible column diff --git a/test/old/testdir/test_syntax.vim b/test/old/testdir/test_syntax.vim index c9ad4bb857..76a21adc57 100644 --- a/test/old/testdir/test_syntax.vim +++ b/test/old/testdir/test_syntax.vim @@ -41,9 +41,9 @@ func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) let l:errors += synID(a:lnum, l:i, a:trans) - \ ->synIDattr("name") - \ ->assert_equal(l:expectedGroups[l:i - 1], - \ l:msg .. l:i) + \ ->synIDattr("name") + \ ->assert_equal(l:expectedGroups[l:i - 1], + \ l:msg .. l:i) endfor endfunc diff --git a/test/old/testdir/test_virtualedit.vim b/test/old/testdir/test_virtualedit.vim index 6ff51e36fb..4780faa706 100644 --- a/test/old/testdir/test_virtualedit.vim +++ b/test/old/testdir/test_virtualedit.vim @@ -564,37 +564,115 @@ func Test_virtualedit_mouse() let save_mouse = &mouse set mouse=a set virtualedit=all - new + botright new + let row = win_screenpos(0)[0] + 20vsplit + wincmd p call setline(1, ["text\tword"]) redraw - call Ntest_setmouse(1, 4) + call Ntest_setmouse(row, 21 + 4) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 4, 0, 4], getcurpos()) - call Ntest_setmouse(1, 5) + call Ntest_setmouse(row, 21 + 5) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 0, 5], getcurpos()) - call Ntest_setmouse(1, 6) + call Ntest_setmouse(row, 21 + 6) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 1, 6], getcurpos()) - call Ntest_setmouse(1, 7) + call Ntest_setmouse(row, 21 + 7) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 2, 7], getcurpos()) - call Ntest_setmouse(1, 8) + call Ntest_setmouse(row, 21 + 8) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 3, 8], getcurpos()) - call Ntest_setmouse(1, 9) + call Ntest_setmouse(row, 21 + 9) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 6, 0, 9], getcurpos()) - call Ntest_setmouse(1, 12) + call Ntest_setmouse(row, 21 + 12) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 9, 0, 12], getcurpos()) - call Ntest_setmouse(1, 13) + call Ntest_setmouse(row, 21 + 13) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 10, 0, 13], getcurpos()) - call Ntest_setmouse(1, 15) + call Ntest_setmouse(row, 21 + 15) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 20) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 7, 20], getcurpos()) + + setlocal nowrap + call setline(2, repeat('a', 19)) + normal! j14zl + redraw + call Ntest_setmouse(row, 21 + 1) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 11) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 1) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 11) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + + setlocal number numberwidth=2 + redraw + call Ntest_setmouse(row, 21 + 3) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + setlocal nonumber + + if has('signs') + sign define Sign1 text=口 + sign place 1 name=Sign1 line=1 + sign place 2 name=Sign1 line=2 + redraw + call Ntest_setmouse(row, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + sign unplace 1 + sign unplace 2 + sign undefine Sign1 + endif + + wincmd h + 4wincmd > + normal! gg24I. + redraw + call Ntest_setmouse(row + 1, 12) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 24 + 9, 0, 24 + 12], getcurpos()) + call Ntest_setmouse(row + 1, 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 24 + 10, 0, 24 + 13], getcurpos()) + call Ntest_setmouse(row + 1, 15) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 24 + 10, 2, 24 + 15], getcurpos()) + call Ntest_setmouse(row + 1, 20) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 24 + 10, 7, 24 + 20], getcurpos()) bwipe! let &mouse = save_mouse diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index a12ecefc73..5d70492451 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -1577,4 +1577,18 @@ func Test_visual_hl_with_showbreak() call StopVimInTerminal(buf) endfunc +func Test_Visual_r_CTRL_C() + new + " visual r_cmd + call setline(1, [' ']) + call feedkeys("\<c-v>$r\<c-c>", 'tx') + call assert_equal([''], getline(1, 1)) + + " visual gr_cmd + call setline(1, [' ']) + call feedkeys("\<c-v>$gr\<c-c>", 'tx') + call assert_equal([''], getline(1, 1)) + bw! +endfu + " vim: shiftwidth=2 sts=2 expandtab |