diff options
117 files changed, 1840 insertions, 877 deletions
diff --git a/.github/workflows/vim_patches.yml b/.github/workflows/vim_patches.yml index b0be01089f..5154565def 100644 --- a/.github/workflows/vim_patches.yml +++ b/.github/workflows/vim_patches.yml @@ -43,7 +43,7 @@ jobs: id: update-version run: | git checkout -b ${VERSION_BRANCH} - nvim -V1 -es -i NONE +'luafile scripts/vimpatch.lua' +q + nvim -l scripts/vimpatch.lua printf 'NEW_PATCHES=%s\n' $([ -z "$(git diff)" ]; echo $?) >> $GITHUB_OUTPUT - name: Automatic PR @@ -5,12 +5,13 @@ 1. Install [build prerequisites](#build-prerequisites) on your system 2. `git clone https://github.com/neovim/neovim` -3. `cd neovim && make CMAKE_BUILD_TYPE=RelWithDebInfo` +3. `cd neovim` - If you want the **stable release**, also run `git checkout stable`. +4. `make CMAKE_BUILD_TYPE=RelWithDebInfo` - If you want to install to a custom location, set `CMAKE_INSTALL_PREFIX`. See also [INSTALL.md](./INSTALL.md#install-from-source). - On BSD, use `gmake` instead of `make`. - To build on Windows, see the [Building on Windows](#building-on-windows) section. _MSVC (Visual Studio) is recommended._ -4. `sudo make install` +5. `sudo make install` - Default install location is `/usr/local` - On Debian/Ubuntu, instead of `sudo make install`, you can try `cd build && cpack -G DEB && sudo dpkg -i nvim-linux64.deb` to build DEB-package and install it. This helps ensure clean removal of installed files. Note: This is an unsupported, "best-effort" feature of the Nvim build. @@ -131,7 +132,8 @@ https://github.com/cascent/neovim-cygwin was built on Cygwin 2.9.0. Newer `libuv 1. From the MSYS2 shell, install these packages: ``` pacman -S \ - mingw-w64-ucrt-x86_64-{gcc,cmake,make,ninja,diffutils} + mingw-w64-ucrt-x86_64-gcc \ + mingw-w64-x86_64-{cmake,make,ninja,diffutils} ``` 2. From the Windows Command Prompt (`cmd.exe`), set up the `PATH` and build. @@ -292,13 +294,13 @@ Platform-specific requirements are listed below. ### Ubuntu / Debian ```sh -sudo apt-get install ninja-build gettext cmake unzip curl build-essential +sudo apt-get install ninja-build gettext cmake curl build-essential ``` ### RHEL / Fedora ``` -sudo dnf -y install ninja-build cmake gcc make unzip gettext curl glibc-gconv-extra +sudo dnf -y install ninja-build cmake gcc make gettext curl glibc-gconv-extra ``` ### openSUSE @@ -310,13 +312,13 @@ sudo zypper install ninja cmake gcc-c++ gettext-tools curl ### Arch Linux ``` -sudo pacman -S base-devel cmake unzip ninja curl +sudo pacman -S base-devel cmake ninja curl ``` ### Alpine Linux ``` -apk add build-base cmake coreutils curl unzip gettext-tiny-dev +apk add build-base cmake coreutils curl gettext-tiny-dev ``` ### Void Linux @@ -380,7 +382,7 @@ or a specific SHA1 like `--override-input neovim-src github:neovim/neovim/89dc8f ### FreeBSD ``` -sudo pkg install cmake gmake sha unzip wget gettext curl +sudo pkg install cmake gmake sha wget gettext curl ``` If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 hash is `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, then this is your issue (that's the `sha256sum` of an empty file). @@ -388,7 +390,7 @@ If you get an error regarding a `sha256sum` mismatch, where the actual SHA-256 h ### OpenBSD ```sh -doas pkg_add gmake cmake unzip curl gettext-tools +doas pkg_add gmake cmake curl gettext-tools ``` Build can sometimes fail when using the top level `Makefile`, apparently due to some third-party component (see [#2445-comment](https://github.com/neovim/neovim/issues/2445#issuecomment-108124236)). The following instructions use CMake: diff --git a/CMakePresets.json b/CMakePresets.json index b863d88608..b47f509d5f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -10,7 +10,7 @@ { "name": "default", "displayName": "RelWithDebInfo", - "description": "Enables optimizations (-O2) with debug information", + "description": "Enables optimizations with debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }, @@ -19,7 +19,7 @@ { "name": "debug", "displayName": "Debug", - "description": "Disables optimizations (-O0), enables debug information", + "description": "No optimizations, enables debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }, @@ -28,7 +28,7 @@ { "name": "release", "displayName": "Release", - "description": "Same as RelWithDebInfo, but disables debug information", + "description": "Optimized for performance, disables debug information", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }, diff --git a/INSTALL.md b/INSTALL.md index 509213fffc..807922e2e3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -302,7 +302,7 @@ Neovim nightly and stable are available on the [snap store](https://snapcraft.io **Stable Builds** ```sh -sudo snap install --beta nvim --classic +sudo snap install nvim --classic ``` **Nightly Builds** @@ -133,6 +133,9 @@ generated-sources benchmark $(FORMAT) $(LINT) $(TEST) doc: | build/.ran-cmake test: $(TEST) +# iwyu-fix-includes can be downloaded from +# https://github.com/include-what-you-use/include-what-you-use/blob/master/fix_includes.py. +# Create a iwyu-fix-includes shell script in your $PATH that invokes the python script. iwyu: build/.ran-cmake $(CMAKE) --preset iwyu $(CMAKE) --build build > build/iwyu.log diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index f73023ae84..602d2e2d03 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -1,8 +1,8 @@ LIBUV_URL https://github.com/libuv/libuv/archive/v1.49.2.tar.gz LIBUV_SHA256 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/f73e649a954b599fc184726c376476e7a5c439ca.tar.gz -LUAJIT_SHA256 bc992b3ae0a8f5f0ebbf141626b7c99fac794c94ec6896d973582525c7ef868d +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/a4f56a459a588ae768801074b46ba0adcfb49eb1.tar.gz +LUAJIT_SHA256 b4120332a4191db9c9da2d81f9f11f0d4504fc4cff2dea0f642d3d8f1fcebd0e LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 @@ -50,11 +50,11 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 d3a423ab66dc62b2969625e280116678a8a22582b5ff087795222108db2f6a6e TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.6.tar.gz -TREESITTER_SHA256 03c7ee1e6f9f4f3821fd4af0ae06e1da60433b304a73ff92ee9694933009121a +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.24.7.tar.gz +TREESITTER_SHA256 7cbc13c974d6abe978cafc9da12d1e79e07e365c42af75e43ec1b5cdc03ed447 -WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.2.tar.gz -WASMTIME_SHA256 6d1c17c756b83f29f629963228e5fa208ba9d6578421ba2cd07132b6a120accb +WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v25.0.3.tar.gz +WASMTIME_SHA256 17850ca356fce6ea8bcd3847692b3233588ddf32ff31fcccac67ad06bcac0a3a UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.80.1.tar.gz UNCRUSTIFY_SHA256 0e2616ec2f78e12816388c513f7060072ff7942b42f1175eb28b24cb75aaec48 diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c8e0dcd0c5..5a6361d45f 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -654,35 +654,22 @@ nvim_del_var({name}) *nvim_del_var()* • {name} Variable name nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* - Echo a message. + Prints a message given by a list of `[text, hl_group]` "chunks". + + Example: >lua + vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +< Parameters: ~ - • {chunks} A list of `[text, hl_group]` arrays, each representing a - text chunk with specified highlight group name or ID. - `hl_group` element can be omitted for no highlight. + • {chunks} List of `[text, hl_group]` pairs, where each is a `text` + string highlighted by the (optional) name or ID `hl_group`. • {history} if true, add to |message-history|. • {opts} Optional parameters. - • verbose: Message is printed as a result of 'verbose' - option. If Nvim was invoked with -V3log_file, the message - will be redirected to the log_file and suppressed from - direct output. - -nvim_err_write({str}) *nvim_err_write()* - Writes a message to the Vim error buffer. Does not append "\n", the - message is buffered (won't display) until a linefeed is written. - - Parameters: ~ - • {str} Message - -nvim_err_writeln({str}) *nvim_err_writeln()* - Writes a message to the Vim error buffer. Appends "\n", so the buffer is - flushed (and displayed). - - Parameters: ~ - • {str} Message - - See also: ~ - • nvim_err_write() + • err: Treat the message like `:echoerr`. Sets `hl_group` + to |hl-ErrorMsg| by default. + • verbose: Message is controlled by the 'verbose' option. + Nvim invoked with `-V3log` will write the message to the + "log" file instead of standard output. nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()* Evaluates statusline string. @@ -775,6 +762,8 @@ nvim_get_api_info() *nvim_get_api_info()* nvim_get_chan_info({chan}) *nvim_get_chan_info()* Gets information about a channel. + See |nvim_list_uis()| for an example of how to get channel info. + Parameters: ~ • {chan} channel_id, or 0 for current channel @@ -796,7 +785,7 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* present if a pty is used (e.g. for conpty on Windows). • "buffer" (optional) Buffer connected to |terminal| instance. • "client" (optional) Info about the peer (client on the other end of - the RPC channel), which it provided via |nvim_set_client_info()|. + the channel), as set by |nvim_set_client_info()|. nvim_get_color_by_name({name}) *nvim_get_color_by_name()* Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or @@ -1079,6 +1068,12 @@ nvim_list_tabpages() *nvim_list_tabpages()* nvim_list_uis() *nvim_list_uis()* Gets a list of dictionaries representing attached UIs. + Example: The Nvim builtin |TUI| sets its channel info as described in + |startup-tui|. In particular, it sets `client.name` to "nvim-tui". So you + can check if the TUI is running by inspecting the client name of each UI: >lua + vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +< + Return: ~ Array of UI dictionaries, each with these keys: • "height" Requested height of the UI @@ -1099,17 +1094,6 @@ nvim_load_context({dict}) *nvim_load_context()* Parameters: ~ • {dict} |Context| map. -nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* - Notify the user with a message - - Relays the call to vim.notify . By default forwards your message in the - echo area but can be overridden to trigger desktop notifications. - - Parameters: ~ - • {msg} Message to display to the user - • {log_level} The log level - • {opts} Reserved for future use. - nvim_open_term({buffer}, {opts}) *nvim_open_term()* Open a terminal instance in a buffer @@ -1127,7 +1111,7 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()* Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you can use Nvim as a "scrollback pager" (for terminals - like kitty): *terminal-scrollback-pager* >lua + like kitty): *ansi-colorize* *terminal-scrollback-pager* >lua vim.api.nvim_create_user_command('TermHl', function() local b = vim.api.nvim_create_buf(false, true) local chan = vim.api.nvim_open_term(b, {}) @@ -1154,13 +1138,6 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()* Return: ~ Channel id, or 0 on error -nvim_out_write({str}) *nvim_out_write()* - Writes a message to the Vim output buffer. Does not append "\n", the - message is buffered (won't display) until a linefeed is written. - - Parameters: ~ - • {str} Message - nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call this to implement "paste", but it's also intended for @@ -1259,25 +1236,23 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) *nvim_set_client_info()* nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) - Self-identifies the client. + Self-identifies the client. Sets the `client` object returned by + |nvim_get_chan_info()|. - The client/plugin/application should call this after connecting, to - provide hints about its identity and purpose, for debugging and - orchestration. + Clients should call this just after connecting, to provide hints for + debugging and orchestration. (Note: Something is better than nothing! + Fields are optional, but at least set `name`.) Can be called more than once; the caller should merge old info if appropriate. Example: library first identifies the channel, then a plugin using that library later identifies itself. - Note: ~ - • "Something is better than nothing". You don't need to include all the - fields. - Attributes: ~ |RPC| only Parameters: ~ - • {name} Short name for the connected client + • {name} Client short-name. Sets the `client.name` field of + |nvim_get_chan_info()|. • {version} Dict describing the version, with these (optional) keys: • "major" major version (defaults to 0 if not set, for no release yet) @@ -3175,11 +3150,13 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* • {config} Map defining the window configuration. Keys: • relative: Sets the window layout to "floating", placed at (row,col) coordinates relative to: - • "editor" The global editor grid + • "cursor" Cursor position in current window. + • "editor" The global editor grid. + • "laststatus" 'laststatus' if present, or last row. + • "mouse" Mouse position. + • "tabline" Tabline if present, or first row. • "win" Window given by the `win` field, or current window. - • "cursor" Cursor position in current window. - • "mouse" Mouse position • win: |window-ID| window to split, or relative window when creating a float (relative="win"). • anchor: Decides which corner of the float to place at diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index f466dde861..6e05dd24d2 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -8282,7 +8282,7 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()* • {skip} (`string|function?`) Return: ~ - (`any`) + (`integer`) searchcount([{options}]) *searchcount()* Get or update the last search count, like what is displayed diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ec98cc844f..4f320aeab3 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -16,8 +16,12 @@ Deprecated features DEPRECATED IN 0.11 *deprecated-0.11* API +• nvim_notify() Use |nvim_echo()| or `nvim_exec_lua("vim.notify(...)", ...)` instead. • nvim_subscribe() Plugins must maintain their own "multicast" channels list. • nvim_unsubscribe() Plugins must maintain their own "multicast" channels list. +• nvim_out_write() Use |nvim_echo()|. +• nvim_err_write() Use |nvim_echo()| with `{err=true}`. +• nvim_err_writeln() Use |nvim_echo()| with `{err=true}`. DIAGNOSTICS • *vim.diagnostic.goto_next()* Use |vim.diagnostic.jump()| with `{count=1, float=true}` instead. diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 3437717467..7d97a18437 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -93,6 +93,10 @@ The {opts} table passed to a handler is the full set of configuration options values in the table are already resolved (i.e. if a user specifies a function for a config option, the function has already been evaluated). +If a diagnostic handler is configured with a "severity" key then the list of +diagnostics passed to that handler will be filtered using the value of that +key (see example below). + Nvim provides these handlers by default: "virtual_text", "signs", and "underline". @@ -119,6 +123,9 @@ with |vim.notify()|: >lua vim.diagnostic.config({ ["my/notify"] = { log_level = vim.log.levels.INFO + + -- This handler will only receive "error" diagnostics. + severity = vim.diagnostic.severity.ERROR, } }) < diff --git a/runtime/doc/health.txt b/runtime/doc/health.txt index bca145bd8e..3d37b88321 100644 --- a/runtime/doc/health.txt +++ b/runtime/doc/health.txt @@ -21,18 +21,7 @@ To run all healthchecks, use: >vim < Plugin authors are encouraged to write new healthchecks. |health-dev| - *g:health* -g:health This global variable controls the behavior and appearance of the - `health` floating window. It should be a dictionary containing the - following optional keys: - - `style`: string? Determines the display style of the `health` window. - Set to `'float'` to enable a floating window. Other - styles are not currently supported. - - Example: >lua - vim.g.health = { style = 'float' } - -Commands *health-commands* +COMMANDS *health-commands* *:che* *:checkhealth* :che[ckhealth] Run all healthchecks. @@ -60,6 +49,23 @@ Commands *health-commands* :checkhealth vim* < +USAGE *health-usage* + +Local mappings in the healthcheck buffer: + +q Closes the window. + +Global configuration: + + *g:health* +g:health Dictionary with the following optional keys: + - `style` (`'float'|nil`) Set to "float" to display :checkhealth in + a floating window instead of the default behavior. + + Example: >lua + vim.g.health = { style = 'float' } + +-------------------------------------------------------------------------------- Create a healthcheck *health-dev* Healthchecks are functions that check the user environment, configuration, or diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 46b3ab507d..f7009aebfe 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -193,6 +193,7 @@ Jump to specific subjects by using tags. This can be done in two ways: Use CTRL-T or CTRL-O to jump back. Use ":q" to close the help window. +Use `yxx` to execute the current Lua/Vimscript code block. If there are several matches for an item you are looking for, this is how you can jump to each one of them: diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 16e6abe294..e8270123d7 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -32,7 +32,7 @@ Follow these steps to get LSP features: Example: >lua vim.lsp.config['luals'] = { -- Command and arguments to start the server. - cmd = { 'lua-language-server' } + cmd = { 'lua-language-server' }, -- Filetypes to automatically attach to. filetypes = { 'lua' }, @@ -93,7 +93,7 @@ Given: >lua multilineTokenSupport = true, } } - } + }, root_markers = { '.git' }, }) @@ -878,7 +878,7 @@ foldexpr({lnum}) *vim.lsp.foldexpr()* To use, check for the "textDocument/foldingRange" capability in an |LspAttach| autocommand. Example: >lua - vim.api.nvim_create_autocommand('LspAttach', { + vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) local client = vim.lsp.get_client_by_id(args.data.client_id) if client:supports_method('textDocument/foldingRange') then diff --git a/runtime/doc/lua-guide.txt b/runtime/doc/lua-guide.txt index b40d5a0791..d0d148f689 100644 --- a/runtime/doc/lua-guide.txt +++ b/runtime/doc/lua-guide.txt @@ -153,7 +153,7 @@ its functions if this succeeds and prints an error message otherwise: if not ok then print("Module had an error") else - mymod.function() + mymod.func() end < In contrast to |:source|, |require()| not only searches through all `lua/` directories diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6547a76f56..44cbf238cf 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -161,7 +161,7 @@ languages like Python and C#. Example: >lua local func_with_opts = function(opts) local will_do_foo = opts.foo local filename = opts.filename - ... + -- ... end func_with_opts { foo = true, filename = "hello.world" } @@ -1299,7 +1299,7 @@ global value of a |global-local| option, see |:setglobal|. A special interface |vim.opt| exists for conveniently interacting with list- -and map-style option from Lua: It allows accessing them as Lua tables and +and map-style options from Lua: It allows accessing them as Lua tables and offers object-oriented method for adding and removing entries. Examples: ~ @@ -1505,7 +1505,7 @@ vim.wo[{winid}][{bufnr}] *vim.wo* Lua module: vim *lua-vim* vim.cmd({command}) *vim.cmd()* - Executes Vim script commands. + Executes Vimscript (|Ex-commands|). Note that `vim.cmd` can be indexed with a command name to return a callable function to the command. @@ -1539,9 +1539,9 @@ vim.cmd({command}) *vim.cmd()* Parameters: ~ • {command} (`string|table`) Command(s) to execute. If a string, - executes multiple lines of Vim script at once. In this - case, it is an alias to |nvim_exec2()|, where `opts.output` - is set to false. Thus it works identical to |:source|. If a + executes multiple lines of Vimscript at once. In this case, + it is an alias to |nvim_exec2()|, where `opts.output` is + set to false. Thus it works identical to |:source|. If a table, executes a single command. In this case, it is an alias to |nvim_cmd()| where `opts` is empty. @@ -1805,7 +1805,7 @@ vim.system({cmd}, {opts}, {on_exit}) *vim.system()* -- Runs synchronously: local obj = vim.system({'echo', 'hello'}, { text = true }):wait() - -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } + -- { code = 0, signal = 0, stdout = 'hello\n', stderr = '' } < See |uv.spawn()| for more details. Note: unlike |uv.spawn()|, vim.system @@ -2428,7 +2428,7 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) function vim.startswith(s, prefix) vim.validate('s', s, 'string') vim.validate('prefix', prefix, 'string') - ... + -- ... end < 2. `vim.validate(spec)` (deprecated) where `spec` is of type @@ -2442,7 +2442,7 @@ vim.validate({name}, {value}, {validator}, {optional}, {message}) age={age, 'number'}, hobbies={hobbies, 'table'}, } - ... + -- ... end < @@ -3148,6 +3148,23 @@ vim.fs.parents({start}) *vim.fs.parents()* (`nil`) (`string?`) +vim.fs.relpath({base}, {target}, {opts}) *vim.fs.relpath()* + Gets `target` path relative to `base`, or `nil` if `base` is not an + ancestor. + + Example: >lua + vim.fs.relpath('/var', '/var/lib') -- 'lib' + vim.fs.relpath('/var', '/usr/bin') -- nil +< + + Parameters: ~ + • {base} (`string`) + • {target} (`string`) + • {opts} (`table?`) Reserved for future use + + Return: ~ + (`string?`) + vim.fs.rm({path}, {opts}) *vim.fs.rm()* WARNING: This feature is experimental/unstable. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 931f5e117c..e6a1adf15b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -68,6 +68,8 @@ DIAGNOSTICS • The "underline" diagnostics handler sorts diagnostics by severity when using the "severity_sort" option. +• Diagnostics are filtered by severity before being passed to a diagnostic + handler |diagnostic-handlers|. EDITOR @@ -94,7 +96,7 @@ EVENTS • `msg_show`: • `history` argument indicating if the message was added to the history. • new message kinds: "bufwrite", "completion", "list_cmd", "lua_print", - "search_cmd", "undo", "wildlist". + "search_cmd", "undo", "verbose", wildlist". HIGHLIGHTS @@ -156,6 +158,11 @@ TREESITTER if no languages are explicitly registered. • |vim.treesitter.language.add()| returns `true` if a parser was loaded successfully and `nil,errmsg` otherwise instead of throwing an error. +• |vim.treesitter.get_parser()| and |vim.treesitter.start()| no longer parse + the tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua + local p = vim.treesitter.get_parser(0, 'c') + p:parse() +< TUI @@ -178,6 +185,9 @@ API • Improved API "meta" docstrings and :help documentation. • |nvim__ns_set()| can set properties for a namespace +• |nvim_echo()| `err` field to print error messages and `chunks` accepts + highlight group IDs. +• |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". DEFAULTS @@ -219,6 +229,7 @@ DIAGNOSTICS EDITOR +• Use |yxx| in :help docs to execute Lua and Vimscript code examples. • Improved |paste| handling for redo (dot-repeat) and macros (|recording|): • Redoing a large paste is significantly faster and ignores 'autoindent'. • Replaying a macro with |@| also replays pasted text. @@ -274,6 +285,7 @@ LUA supporting two new parameters, `encoding` and `strict_indexing`. • |vim.json.encode()| has an option to enable forward slash escaping • |vim.fs.abspath()| converts paths to absolute paths. +• |vim.fs.relpath()| gets relative path compared to base path. OPTIONS @@ -289,6 +301,12 @@ PERFORMANCE inflight requests). This greatly improves performance with slow LSP servers. • 10x speedup for |vim.treesitter.foldexpr()| (when no parser exists for the buffer). +• Strong |treesitter-query| caching makes repeat |vim.treesitter.query.get()| + and |vim.treesitter.query.parse()| calls significantly faster for large + queries. +• Treesitter highlighting is now asynchronous. To force synchronous parsing, + use `vim.g._ts_force_sync_parsing = true`. +• Treesitter folding is now calculated asynchronously. PLUGINS @@ -331,6 +349,8 @@ TREESITTER • New |TSNode:child_with_descendant()|, which is nearly identical to |TSNode:child_containing_descendant()| except that it can return the descendant itself. +• |LanguageTree:parse()| optionally supports asynchronous invocation, which is + activated by passing the `on_parse` callback parameter. TUI @@ -359,8 +379,8 @@ UI • |vim.diagnostic.setqflist()| updates an existing quickfix list with the given title if found • |ui-messages| content chunks now also contain the highlight group ID. -• |:checkhealth| can be display in a floating window and controlled by - the |g:health| variable. +• |:checkhealth| can display in a floating window, controlled by the + |g:health| variable. ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c2ed19f34f..8d171183d6 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1560,8 +1560,8 @@ A jump table for the options with a short description can be found at |Q_op|. "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are present, - "noselect" has precedence. + pre-selected. If both "noinsert" and "noselect" are + present, "noselect" has precedence. fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where @@ -4657,8 +4657,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'redrawtime' 'rdt' number (default 2000) global Time in milliseconds for redrawing the display. Applies to - 'hlsearch', 'inccommand', |:match| highlighting and syntax - highlighting. + 'hlsearch', 'inccommand', |:match| highlighting, syntax highlighting, + and async |LanguageTree:parse()|. When redrawing takes more than this many milliseconds no further matches will be highlighted. For syntax highlighting the time applies per window. When over the diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index a291c0277d..70082c7835 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -538,9 +538,9 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: < Otherwise it works the same as `:ldo`. FILTERING A QUICKFIX OR LOCATION LIST: - *cfilter-plugin* *:Cfilter* *:Lfilter* + *cfilter-plugin* *:Cfilter* *:Lfilter* *package-cfilter* If you have too many entries in a quickfix list, you can use the cfilter -plugin to reduce the number of entries. Load the plugin with: > +plugin to reduce the number of entries. Load the plugin with: >vim packadd cfilter diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 0bfbea75fb..c0254c3fa1 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -164,8 +164,7 @@ argument. you can overwrite a file by adding an exclamation mark to the Ex command, as in ":w!". The 'readonly' option can be reset with ":set noro" (see the options chapter, |options|). - Subsequent edits will not be done in readonly mode. Calling - the executable "view" has the same effect as the -R argument. + Subsequent edits will not be done in readonly mode. The 'updatecount' option will be set to 10000, meaning that the swap file will not be updated automatically very often. See |-M| for disallowing modifications. @@ -226,7 +225,8 @@ argument. arguments. The {script} name is stored at `_G.arg[0]`. Sets 'verbose' to 1 (like "-V1"), so Lua `print()` writes to - output. + output, as well as other message-emitting functions like + |:echo|. If {script} prints messages and doesn't cause Nvim to exit, Nvim ensures output ends with a newline. @@ -288,21 +288,18 @@ argument. command from a script. |debug-mode| *-n* --n No |swap-file| will be used. Recovery after a crash will be - impossible. Handy if you want to view or edit a file on a - very slow medium (e.g., a floppy). - Can also be done with ":set updatecount=0". You can switch it - on again by setting the 'updatecount' option to some value, - e.g., ":set uc=100". - 'updatecount' is set to 0 AFTER executing commands from a - vimrc file, but before the GUI initializations. Thus it - overrides a setting for 'updatecount' in a vimrc file, but not - in a gvimrc file. See |startup|. - When you want to reduce accesses to the disk (e.g., for a - laptop), don't use "-n", but set 'updatetime' and - 'updatecount' to very big numbers, and type ":preserve" when - you want to save your work. This way you keep the possibility - for crash recovery. +-n Disables |swap-file| by setting 'updatecount' to 0 (after + executing any |vimrc|). Recovery after a crash will be + impossible. Improves peformance when working with a file on + a very slow medium (usb drive, network share). + + Enable it again by setting 'updatecount' to some value, e.g. + ":set updatecount=100". + + To reduce accesses to the disk, don't use "-n", but set + 'updatetime' and 'updatecount' to very big numbers, and type + ":preserve" when you want to save your work. This way you + keep the possibility for crash recovery. *-o* -o[N] Open N windows, split horizontally. If [N] is not given, diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index ff8e3a976f..a7f278990c 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -165,8 +165,8 @@ directory indicated in the request. >lua end }) -To try it out, select the above code and source it with `:'<,'>lua`, then run -this command in a :terminal buffer: > +To try it out, select the above code and source it with `:'<,'>lua` (or +`yxx`), then run this command in a :terminal buffer: > printf "\033]7;file://./foo/bar\033\\" @@ -207,7 +207,7 @@ Use |jobwait()| to check if the terminal job has finished: >vim let running = jobwait([&channel], 0)[0] == -1 < ============================================================================== -:Termdebug plugin *terminal-debug* +:Termdebug plugin *terminal-debug* *terminal-debugger* *package-termdebug* The Terminal debugging plugin can be used to debug a program with gdb and view the source code in a Vim window. Since this is completely contained inside diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 28fe943359..41679f80ca 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -70,7 +70,7 @@ adds arbitrary metadata and conditional data to a match. Queries are written in a lisp-like language documented in https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax -Note: The predicates listed there page differ from those Nvim supports. See +Note: The predicates listed there differ from those Nvim supports. See |treesitter-predicates| for a complete list of predicates supported by Nvim. Nvim looks for queries as `*.scm` files in a `queries` directory under @@ -1090,6 +1090,9 @@ start({bufnr}, {lang}) *vim.treesitter.start()* required for some plugins. In this case, add `vim.bo.syntax = 'on'` after the call to `start`. + Note: By default, the highlighter parses code asynchronously, using a + segment time of 3ms. + Example: >lua vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', callback = function(args) @@ -1336,7 +1339,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()* `info.captures`). • `info.patterns`: information about predicates. - Example (select the code then run `:'<,'>lua` to try it): >lua + Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): >lua local query = vim.treesitter.query.parse('vimdoc', [[ ; query ((h1) @str @@ -1401,8 +1404,8 @@ Query:iter_captures({node}, {source}, {start}, {stop}) Defaults to `node:end_()`. Return: ~ - (`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch`) - capture id, capture node, metadata, match + (`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`) + capture id, capture node, metadata, match, tree *Query:iter_matches()* Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) @@ -1422,7 +1425,7 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) -- `node` was captured by the `name` capture in the match local node_data = metadata[id] -- Node level metadata - ... use the info here ... + -- ... use the info here ... end end end @@ -1447,8 +1450,8 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) compatibility and will be removed in a future release. Return: ~ - (`fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata`) - pattern id, match, metadata + (`fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree`) + pattern id, match, metadata, tree set({lang}, {query_name}, {text}) *vim.treesitter.query.set()* Sets the runtime query named {query_name} for {lang} @@ -1611,7 +1614,7 @@ LanguageTree:node_for_range({range}, {opts}) Return: ~ (`TSNode?`) -LanguageTree:parse({range}) *LanguageTree:parse()* +LanguageTree:parse({range}, {on_parse}) *LanguageTree:parse()* Recursively parse all regions in the language tree using |treesitter-parsers| for the corresponding languages and run injection queries on the parsed trees to determine whether child trees should be @@ -1622,14 +1625,27 @@ LanguageTree:parse({range}) *LanguageTree:parse()* if {range} is `true`). Parameters: ~ - • {range} (`boolean|Range?`) Parse this range in the parser's source. - 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). + • {range} (`boolean|Range?`) Parse this range in the parser's + source. 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). + • {on_parse} (`fun(err?: string, trees?: table<integer, TSTree>)?`) + Function invoked when parsing completes. When provided and + `vim.g._ts_force_sync_parsing` is not set, parsing will + run asynchronously. The first argument to the function is + a string respresenting the error type, in case of a + failure (currently only possible for timeouts). The second + argument is the list of trees returned by the parse (upon + success), or `nil` if the parse timed out (determined by + 'redrawtime'). + + If parsing was still able to finish synchronously (within + 3ms), `parse()` returns the list of trees. Otherwise, it + returns `nil`. Return: ~ - (`table<integer, TSTree>`) + (`table<integer, TSTree>?`) *LanguageTree:register_cbs()* LanguageTree:register_cbs({cbs}, {recursive}) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 8f25133e7a..26ea03d2be 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -806,6 +806,7 @@ must handle. "search_cmd" Entered search command "search_count" Search count message ("S" flag of 'shortmess') "undo" |:undo| and |:redo| message + "verbose" 'verbose' message "wildlist" 'wildmode' "list" message "wmsg" Warning ("search hit BOTTOM", |W10|, …) New kinds may be added in the future; clients should treat unknown diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index 1fc612de26..f8cfcbe547 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -684,6 +684,13 @@ Summary: *help-summary* > :help E128 < takes you to the |:function| command +27) Documenction for packages distributed with Vim have the form package-<name>. + So > + :help package-termdebug +< + will bring you to the help section for the included termdebug plugin and + how to enable it. + ============================================================================== diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 698d1207d3..d75438cd22 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -235,7 +235,7 @@ an archive or as a repository. For an archive you can follow these steps: else. -Adding nohlsearch package *nohlsearch-install* +Adding nohlsearch package *nohlsearch-install* *package-nohlsearch* Load the plugin with this command: > packadd nohlsearch diff --git a/runtime/doc/usr_25.txt b/runtime/doc/usr_25.txt index 955d2ae5f0..8dbe1332b5 100644 --- a/runtime/doc/usr_25.txt +++ b/runtime/doc/usr_25.txt @@ -190,15 +190,15 @@ This results in the following: story. ~ -JUSTIFYING TEXT +JUSTIFYING TEXT *justify* *:Justify* *Justify()* *package-justify* Vim has no built-in way of justifying text. However, there is a neat macro package that does the job. To use this package, execute the following -command: > +command: >vim :packadd justify -Or put this line in your |vimrc|: > +Or put this line in your |vimrc|: >vim packadd! justify diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 6074931565..611e820cab 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -554,6 +554,11 @@ gO Show a filetype-specific, navigable "outline" of the *:sl!* *:sleep!* :[N]sl[eep]! [N][m] Same as above, but hide the cursor. + *yxx* +yxx Executes the current code block. + + Works in |help| buffers. + ============================================================================== 2. Using Vim like less or more *less* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index a92ddf33e6..a59312208a 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -347,11 +347,15 @@ Options: - `:set {option}<` removes local value for all |global-local| options. - `:setlocal {option}<` copies global value to local value for all options. +- 'ambiwidth' cannot be set to empty. - 'autoread' works in the terminal (if it supports "focus" events) +- 'background' cannot be set to empty. - 'cpoptions' flags: |cpo-_| - 'diffopt' "linematch" feature +- 'eadirection' cannot be set to empty. - 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. +- 'fileformat' cannot be set to empty. - 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown", "vertleft", "vertright", "verthoriz" - 'foldcolumn' supports up to 9 dynamic/fixed columns @@ -363,14 +367,17 @@ Options: - "clean" removes unloaded buffers from the jumplist. - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. - 'laststatus' global statusline support +- 'mousemodel' cannot be set to empty. - 'mousescroll' amount to scroll by when scrolling with a mouse - 'pumblend' pseudo-transparent popupmenu - 'scrollback' - 'shortmess' - "F" flag does not affect output from autocommands. - "q" flag fully hides macro recording message. +- 'showcmdloc' cannot be set to empty. - 'signcolumn' can show multiple signs (dynamic or fixed columns) - 'statuscolumn' full control of columns using 'statusline' format +- 'splitkeep' cannot be set to empty. - 'tabline' middle-click on tabpage label closes tabpage, and %@Func@foo%X can call any function on mouse-click - 'termpastefilter' @@ -423,8 +430,11 @@ TUI: < *'term'* *E529* *E530* *E531* - 'term' reflects the terminal type derived from |$TERM| and other environment - checks. For debugging only; not reliable during startup. >vim - :echo &term + checks. Use `:echo &term` to get its value. For debugging only; not + reliable during startup. + - Note: If you want to detect when Nvim is running in a terminal, use + `has('gui_running')` |has()| or see |nvim_list_uis()| for an example of + how to inspect the UI channel. - "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. - Nvim will use 256-colour capability on Linux virtual terminals. Vim uses diff --git a/runtime/ftplugin/editorconfig.vim b/runtime/ftplugin/editorconfig.vim index 6d437351eb..1693a95c0b 100644 --- a/runtime/ftplugin/editorconfig.vim +++ b/runtime/ftplugin/editorconfig.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: EditorConfig " Maintainer: Riley Bruins <ribru17@gmail.com> -" Last Change: 2024 Jul 06 +" Last Change: 2025 Jan 10 if exists('b:did_ftplugin') finish @@ -10,4 +10,6 @@ let b:did_ftplugin = 1 setl comments=:#,:; commentstring=#\ %s -let b:undo_ftplugin = 'setl com< cms<' +setl omnifunc=syntaxcomplete#Complete + +let b:undo_ftplugin = 'setl com< cms< ofu<' diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index 8d991be0e4..689a4db408 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -31,5 +31,56 @@ vim.keymap.set('n', 'gO', function() require('vim.vimhelp').show_toc() end, { buffer = 0, silent = true }) -vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n exe "nunmap <buffer> gO"' +-- Add "runnables" for Lua/Vimscript code examples. +---@type table<integer, { lang: string, code: string }> +local code_blocks = {} +local tree = vim.treesitter.get_parser():parse()[1] +local query = vim.treesitter.query.parse( + 'vimdoc', + [[ + (codeblock + (language) @_lang + . + (code) @code + (#any-of? @_lang "lua" "vim") + (#set! @code lang @_lang)) +]] +) +local run_message_ns = vim.api.nvim_create_namespace('vimdoc/run_message') + +vim.api.nvim_buf_clear_namespace(0, run_message_ns, 0, -1) +for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do + for id, nodes in pairs(match) do + local name = query.captures[id] + local node = nodes[1] + local start, _, end_ = node:parent():range() --[[@as integer]] + + if name == 'code' then + vim.api.nvim_buf_set_extmark(0, run_message_ns, start, 0, { + virt_text = { { 'Run with `yxx`', 'LspCodeLens' } }, + }) + local code = vim.treesitter.get_node_text(node, 0) + local lang_node = match[metadata[id].lang][1] --[[@as TSNode]] + local lang = vim.treesitter.get_node_text(lang_node, 0) + for i = start + 1, end_ do + code_blocks[i] = { lang = lang, code = code } + end + end + end +end + +vim.keymap.set('n', 'yxx', function() + local pos = vim.api.nvim_win_get_cursor(0)[1] + local code_block = code_blocks[pos] + if not code_block then + vim.print('No code block found') + elseif code_block.lang == 'lua' then + vim.cmd.lua(code_block.code) + elseif code_block.lang == 'vim' then + vim.cmd(code_block.code) + end +end, { buffer = true }) + +vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') + .. '\n exe "nunmap <buffer> gO" | exe "nunmap <buffer> yxx"' vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | call v:lua.vim.treesitter.stop()' diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 4216a2acb7..d71116117e 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -224,7 +224,7 @@ do local function cmd(opts) local ok, err = pcall(vim.api.nvim_cmd, opts, {}) if not ok then - vim.api.nvim_err_writeln(err:sub(#'Vim:' + 1)) + vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true }) end end diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 44f17b3f85..4b28b63746 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -58,6 +58,7 @@ vim._extra = { --- @private vim.log = { + --- @enum vim.log.levels levels = { TRACE = 0, DEBUG = 1, @@ -92,7 +93,7 @@ local utfs = { --- --- -- Runs synchronously: --- local obj = vim.system({'echo', 'hello'}, { text = true }):wait() ---- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } +--- -- { code = 0, signal = 0, stdout = 'hello\n', stderr = '' } --- --- ``` --- @@ -390,7 +391,7 @@ end local VIM_CMD_ARG_MAX = 20 ---- Executes Vim script commands. +--- Executes Vimscript (|Ex-commands|). --- --- Note that `vim.cmd` can be indexed with a command name to return a callable function to the --- command. @@ -425,7 +426,7 @@ local VIM_CMD_ARG_MAX = 20 --- ``` --- ---@param command string|table Command(s) to execute. ---- If a string, executes multiple lines of Vim script at once. In this +--- If a string, executes multiple lines of Vimscript at once. In this --- case, it is an alias to |nvim_exec2()|, where `opts.output` is set --- to false. Thus it works identical to |:source|. --- If a table, executes a single command. In this case, it is an alias @@ -620,13 +621,8 @@ end ---@param opts table|nil Optional parameters. Unused by default. ---@diagnostic disable-next-line: unused-local function vim.notify(msg, level, opts) -- luacheck: no unused args - if level == vim.log.levels.ERROR then - vim.api.nvim_err_writeln(msg) - elseif level == vim.log.levels.WARN then - vim.api.nvim_echo({ { msg, 'WarningMsg' } }, true, {}) - else - vim.api.nvim_echo({ { msg } }, true, {}) - end + local chunks = { { msg, level == vim.log.levels.WARN and 'WarningMsg' or nil } } + vim.api.nvim_echo(chunks, true, { err = level == vim.log.levels.ERROR }) end do diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 7297c8ad38..670e867c1e 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1097,29 +1097,28 @@ function vim.api.nvim_del_user_command(name) end --- @param name string Variable name function vim.api.nvim_del_var(name) end ---- Echo a message. +--- Prints a message given by a list of `[text, hl_group]` "chunks". --- ---- @param chunks any[] A list of `[text, hl_group]` arrays, each representing a ---- text chunk with specified highlight group name or ID. ---- `hl_group` element can be omitted for no highlight. +--- Example: +--- ```lua +--- vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +--- ``` +--- +--- @param chunks any[] List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +--- the (optional) name or ID `hl_group`. --- @param history boolean if true, add to `message-history`. --- @param opts vim.api.keyset.echo_opts Optional parameters. ---- - verbose: Message is printed as a result of 'verbose' option. ---- If Nvim was invoked with -V3log_file, the message will be ---- redirected to the log_file and suppressed from direct output. +--- - err: Treat the message like `:echoerr`. Sets `hl_group` to `hl-ErrorMsg` by default. +--- - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +--- will write the message to the "log" file instead of standard output. function vim.api.nvim_echo(chunks, history, opts) end ---- Writes a message to the Vim error buffer. Does not append "\n", the ---- message is buffered (won't display) until a linefeed is written. ---- ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_err_write(str) end ---- Writes a message to the Vim error buffer. Appends "\n", so the buffer is ---- flushed (and displayed). ---- ---- @see vim.api.nvim_err_write ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_err_writeln(str) end --- Evaluates a Vimscript `expression`. Dicts and Lists are recursively expanded. @@ -1279,6 +1278,8 @@ function vim.api.nvim_get_autocmds(opts) end --- Gets information about a channel. --- +--- See `nvim_list_uis()` for an example of how to get channel info. +--- --- @param chan integer channel_id, or 0 for current channel --- @return table<string,any> # Channel info dict with these keys: --- - "id" Channel id. @@ -1296,8 +1297,8 @@ function vim.api.nvim_get_autocmds(opts) end --- "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. --- for conpty on Windows). --- - "buffer" (optional) Buffer connected to |terminal| instance. ---- - "client" (optional) Info about the peer (client on the other end of the RPC channel), ---- which it provided via |nvim_set_client_info()|. +--- - "client" (optional) Info about the peer (client on the other end of the channel), as set +--- by |nvim_set_client_info()|. --- function vim.api.nvim_get_chan_info(chan) end @@ -1619,6 +1620,14 @@ function vim.api.nvim_list_tabpages() end --- Gets a list of dictionaries representing attached UIs. --- +--- Example: The Nvim builtin `TUI` sets its channel info as described in `startup-tui`. In +--- particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +--- inspecting the client name of each UI: +--- +--- ```lua +--- vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +--- ``` +--- --- @return any[] # Array of UI dictionaries, each with these keys: --- - "height" Requested height of the UI --- - "width" Requested width of the UI @@ -1638,14 +1647,10 @@ function vim.api.nvim_list_wins() end --- @return any function vim.api.nvim_load_context(dict) end ---- Notify the user with a message ---- ---- Relays the call to vim.notify . By default forwards your message in the ---- echo area but can be overridden to trigger desktop notifications. ---- ---- @param msg string Message to display to the user ---- @param log_level integer The log level ---- @param opts table<string,any> Reserved for future use. +--- @deprecated +--- @param msg string +--- @param log_level integer +--- @param opts table<string,any> --- @return any function vim.api.nvim_notify(msg, log_level, opts) end @@ -1664,7 +1669,8 @@ function vim.api.nvim_notify(msg, log_level, opts) end --- in a virtual terminal having the intended size. --- --- Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you ---- can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +--- can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +--- [terminal-scrollback-pager]() --- --- ```lua --- vim.api.nvim_create_user_command('TermHl', function() @@ -1748,10 +1754,12 @@ function vim.api.nvim_open_term(buffer, opts) end --- @param config vim.api.keyset.win_config Map defining the window configuration. Keys: --- - relative: Sets the window layout to "floating", placed at (row,col) --- coordinates relative to: ---- - "editor" The global editor grid ---- - "win" Window given by the `win` field, or current window. ---- - "cursor" Cursor position in current window. ---- - "mouse" Mouse position +--- - "cursor" Cursor position in current window. +--- - "editor" The global editor grid. +--- - "laststatus" 'laststatus' if present, or last row. +--- - "mouse" Mouse position. +--- - "tabline" Tabline if present, or first row. +--- - "win" Window given by the `win` field, or current window. --- - win: `window-ID` window to split, or relative window when creating a --- float (relative="win"). --- - anchor: Decides which corner of the float to place at (row,col): @@ -1859,10 +1867,8 @@ function vim.api.nvim_open_term(buffer, opts) end --- @return integer # Window handle, or 0 on error function vim.api.nvim_open_win(buffer, enter, config) end ---- Writes a message to the Vim output buffer. Does not append "\n", the ---- message is buffered (won't display) until a linefeed is written. ---- ---- @param str string Message +--- @deprecated +--- @param str string function vim.api.nvim_out_write(str) end --- Parse command line. diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index c08ab0663b..98e916115e 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -88,6 +88,7 @@ error('Cannot require a meta file') --- @field pattern? string|string[] --- @class vim.api.keyset.echo_opts +--- @field err? boolean --- @field verbose? boolean --- @class vim.api.keyset.empty diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e5cea884c5..14f252516a 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -52,7 +52,7 @@ vim.go.ari = vim.go.allowrevins --- set to one of CJK locales. See Unicode Standard Annex #11 --- (https://www.unicode.org/reports/tr11). --- ---- @type string +--- @type 'single'|'double' vim.o.ambiwidth = "single" vim.o.ambw = vim.o.ambiwidth vim.go.ambiwidth = vim.o.ambiwidth @@ -208,7 +208,7 @@ vim.go.awa = vim.go.autowriteall --- will change. To use other settings, place ":highlight" commands AFTER --- the setting of the 'background' option. --- ---- @type string +--- @type 'light'|'dark' vim.o.background = "dark" vim.o.bg = vim.o.background vim.go.background = vim.o.background @@ -595,7 +595,7 @@ vim.wo.briopt = vim.wo.breakindentopt --- This option is used together with 'buftype' and 'swapfile' to specify --- special kinds of buffers. See `special-buffers`. --- ---- @type string +--- @type ''|'hide'|'unload'|'delete'|'wipe' vim.o.bufhidden = "" vim.o.bh = vim.o.bufhidden vim.bo.bufhidden = vim.o.bufhidden @@ -658,7 +658,7 @@ vim.bo.bl = vim.bo.buflisted --- without saving. For writing there must be matching `BufWriteCmd|, --- |FileWriteCmd` or `FileAppendCmd` autocommands. --- ---- @type string +--- @type ''|'acwrite'|'help'|'nofile'|'nowrite'|'quickfix'|'terminal'|'prompt' vim.o.buftype = "" vim.o.bt = vim.o.buftype vim.bo.buftype = vim.o.buftype @@ -1087,8 +1087,8 @@ vim.go.cia = vim.go.completeitemalign --- "menu" or "menuone". No effect if "longest" is present. --- --- noselect Same as "noinsert", except that no menu item is ---- pre-selected. If both "noinsert" and "noselect" are present, ---- "noselect" has precedence. +--- pre-selected. If both "noinsert" and "noselect" are +--- present, "noselect" has precedence. --- --- fuzzy Enable `fuzzy-matching` for completion candidates. This --- allows for more flexible and intuitive matching, where @@ -1118,7 +1118,7 @@ vim.go.cot = vim.go.completeopt --- For Insert mode completion the buffer-local value is used. For --- command line completion the global value is used. --- ---- @type string +--- @type ''|'slash'|'backslash' vim.o.completeslash = "" vim.o.csl = vim.o.completeslash vim.bo.completeslash = vim.o.completeslash @@ -1824,7 +1824,7 @@ vim.go.dy = vim.go.display --- hor horizontally, height of windows is not affected --- both width and height of windows is affected --- ---- @type string +--- @type 'both'|'ver'|'hor' vim.o.eadirection = "both" vim.o.ead = vim.o.eadirection vim.go.eadirection = vim.o.eadirection @@ -2126,7 +2126,7 @@ vim.go.fencs = vim.go.fileencodings --- option is set, because the file would be different when written. --- This option cannot be changed when 'modifiable' is off. --- ---- @type string +--- @type 'unix'|'dos'|'mac' vim.o.fileformat = "unix" vim.o.ff = vim.o.fileformat vim.bo.fileformat = vim.o.fileformat @@ -2382,7 +2382,7 @@ vim.go.fcl = vim.go.foldclose --- "[1-9]": to display a fixed number of columns --- See `folding`. --- ---- @type string +--- @type 'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' vim.o.foldcolumn = "0" vim.o.fdc = vim.o.foldcolumn vim.wo.foldcolumn = vim.o.foldcolumn @@ -2479,7 +2479,7 @@ vim.wo.fmr = vim.wo.foldmarker --- `fold-syntax` syntax Syntax highlighting items specify folds. --- `fold-diff` diff Fold text that is not changed. --- ---- @type string +--- @type 'manual'|'expr'|'marker'|'indent'|'syntax'|'diff' vim.o.foldmethod = "manual" vim.o.fdm = vim.o.foldmethod vim.wo.foldmethod = vim.o.foldmethod @@ -3144,7 +3144,7 @@ vim.bo.ims = vim.bo.imsearch --- 'redrawtime') then 'inccommand' is automatically disabled until --- `Command-line-mode` is done. --- ---- @type string +--- @type 'nosplit'|'split'|'' vim.o.inccommand = "nosplit" vim.o.icm = vim.o.inccommand vim.go.inccommand = vim.o.inccommand @@ -4354,7 +4354,7 @@ vim.go.mh = vim.go.mousehide --- "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click) --- "g<RightMouse>" is "<C-RightMouse> ("CTRL-T") --- ---- @type string +--- @type 'extend'|'popup'|'popup_setpos' vim.o.mousemodel = "popup_setpos" vim.o.mousem = vim.o.mousemodel vim.go.mousemodel = vim.o.mousemodel @@ -4845,8 +4845,8 @@ vim.go.redrawdebug = vim.o.redrawdebug vim.go.rdb = vim.go.redrawdebug --- Time in milliseconds for redrawing the display. Applies to ---- 'hlsearch', 'inccommand', `:match` highlighting and syntax ---- highlighting. +--- 'hlsearch', 'inccommand', `:match` highlighting, syntax highlighting, +--- and async `LanguageTree:parse()`. --- When redrawing takes more than this many milliseconds no further --- matches will be highlighted. --- For syntax highlighting the time applies per window. When over the @@ -4947,7 +4947,7 @@ vim.wo.rl = vim.wo.rightleft --- This is useful for languages such as Hebrew, Arabic and Farsi. --- The 'rightleft' option must be set for 'rightleftcmd' to take effect. --- ---- @type string +--- @type 'search' vim.o.rightleftcmd = "search" vim.o.rlc = vim.o.rightleftcmd vim.wo.rightleftcmd = vim.o.rightleftcmd @@ -5222,7 +5222,7 @@ vim.go.sect = vim.go.sections --- backwards, you cannot include the last character of a line, when --- starting in Normal mode and 'virtualedit' empty. --- ---- @type string +--- @type 'inclusive'|'exclusive'|'old' vim.o.selection = "inclusive" vim.o.sel = vim.o.selection vim.go.selection = vim.o.selection @@ -5788,7 +5788,7 @@ vim.go.sc = vim.go.showcmd --- place the text. Without a custom 'statusline' or 'tabline' it will be --- displayed in a convenient location. --- ---- @type string +--- @type 'last'|'statusline'|'tabline' vim.o.showcmdloc = "last" vim.o.sloc = vim.o.showcmdloc vim.go.showcmdloc = vim.o.showcmdloc @@ -5920,7 +5920,7 @@ vim.go.siso = vim.go.sidescrolloff --- "number" display signs in the 'number' column. If the number --- column is not present, then behaves like "auto". --- ---- @type string +--- @type 'yes'|'no'|'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'yes:1'|'yes:2'|'yes:3'|'yes:4'|'yes:5'|'yes:6'|'yes:7'|'yes:8'|'yes:9'|'number' vim.o.signcolumn = "auto" vim.o.scl = vim.o.signcolumn vim.wo.signcolumn = vim.o.signcolumn @@ -6228,7 +6228,7 @@ vim.go.sb = vim.go.splitbelow --- with the previous cursor position. For "screen", the text cannot always --- be kept on the same screen line when 'wrap' is enabled. --- ---- @type string +--- @type 'cursor'|'screen'|'topline' vim.o.splitkeep = "cursor" vim.o.spk = vim.o.splitkeep vim.go.splitkeep = vim.o.splitkeep @@ -6876,7 +6876,7 @@ vim.go.tbs = vim.go.tagbsearch --- match Match case --- smart Ignore case unless an upper case letter is used --- ---- @type string +--- @type 'followic'|'ignore'|'match'|'followscs'|'smart' vim.o.tagcase = "followic" vim.o.tc = vim.o.tagcase vim.bo.tagcase = vim.o.tagcase @@ -7758,7 +7758,7 @@ vim.go.wop = vim.go.wildoptions --- key is never used for the menu. --- This option is not used for <F10>; on Win32. --- ---- @type string +--- @type 'yes'|'menu'|'no' vim.o.winaltkeys = "menu" vim.o.wak = vim.o.winaltkeys vim.go.winaltkeys = vim.o.winaltkeys diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 031b109b38..6316ab2bfc 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -7537,7 +7537,7 @@ function vim.fn.screenstring(row, col) end --- @param stopline? integer --- @param timeout? integer --- @param skip? string|function ---- @return any +--- @return integer function vim.fn.search(pattern, flags, stopline, timeout, skip) end --- Get or update the last search count, like what is displayed diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index dc37595578..8338c5ead7 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -768,7 +768,7 @@ end --- --- --- A special interface |vim.opt| exists for conveniently interacting with list- ---- and map-style option from Lua: It allows accessing them as Lua tables and +--- and map-style options from Lua: It allows accessing them as Lua tables and --- offers object-oriented method for adding and removing entries. --- --- Examples: ~ diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 90f967fe79..6466c7d6e8 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1395,10 +1395,6 @@ M.handlers.signs = { return end - if opts.signs and opts.signs.severity then - diagnostics = filter_by_severity(opts.signs.severity, diagnostics) - end - -- 10 is the default sign priority when none is explicitly specified local priority = opts.signs and opts.signs.priority or 10 local get_priority = severity_to_extmark_priority(priority, opts) @@ -1501,10 +1497,6 @@ M.handlers.underline = { return end - if opts.underline and opts.underline.severity then - diagnostics = filter_by_severity(opts.underline.severity, diagnostics) - end - local ns = M.get_namespace(namespace) if not ns.user_data.underline_ns then ns.user_data.underline_ns = @@ -1565,7 +1557,6 @@ M.handlers.virtual_text = { return end - local severity --- @type vim.diagnostic.SeverityFilter? if opts.virtual_text then if opts.virtual_text.format then diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics) @@ -1576,9 +1567,6 @@ M.handlers.virtual_text = { then diagnostics = prefix_source(diagnostics) end - if opts.virtual_text.severity then - severity = opts.virtual_text.severity - end end local ns = M.get_namespace(namespace) @@ -1590,9 +1578,6 @@ M.handlers.virtual_text = { local virt_text_ns = ns.user_data.virt_text_ns local buffer_line_diagnostics = diagnostic_lines(diagnostics) for line, line_diagnostics in pairs(buffer_line_diagnostics) do - if severity then - line_diagnostics = filter_by_severity(severity, line_diagnostics) - end local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text) if virt_texts then @@ -1797,7 +1782,8 @@ function M.show(namespace, bufnr, diagnostics, opts) for handler_name, handler in pairs(M.handlers) do if handler.show and opts_res[handler_name] then - handler.show(namespace, bufnr, diagnostics, opts_res) + local filtered = filter_by_severity(opts_res[handler_name].severity, diagnostics) + handler.show(namespace, bufnr, filtered, opts_res) end end end diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index dee1bd88ca..1960bca52b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1574,6 +1574,11 @@ local filename = { ['.gitmodules'] = 'gitconfig', ['.gitattributes'] = 'gitattributes', ['.gitignore'] = 'gitignore', + ['.ignore'] = 'gitignore', + ['.dockerignore'] = 'gitignore', + ['.npmignore'] = 'gitignore', + ['.rgignore'] = 'gitignore', + ['.vscodeignore'] = 'gitignore', ['gitolite.conf'] = 'gitolite', ['git-rebase-todo'] = 'gitrebase', gkrellmrc = 'gkrellmrc', @@ -2369,6 +2374,8 @@ local pattern = { ['%.html%.m4$'] = 'htmlm4', ['^JAM.*%.'] = starsetf('jam'), ['^Prl.*%.'] = starsetf('jam'), + ['^${HOME}/.*/Code/User/.*%.json$'] = 'jsonc', + ['^${HOME}/.*/VSCodium/User/.*%.json$'] = 'jsonc', ['%.properties_..$'] = 'jproperties', ['%.properties_.._..$'] = 'jproperties', ['%.properties_.._.._'] = starsetf('jproperties'), diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 2d989fdbac..30a9951f6a 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -757,7 +757,7 @@ function M.html(_, bufnr) if matchregex( line, - [[@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content\|{{.*}}]] + [[@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content]] ) then return 'htmlangular' diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 04a6e43db1..91e06688b3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -741,4 +741,37 @@ function M.abspath(path) return M.joinpath(cwd, path) end +--- Gets `target` path relative to `base`, or `nil` if `base` is not an ancestor. +--- +--- Example: +--- +--- ```lua +--- vim.fs.relpath('/var', '/var/lib') -- 'lib' +--- vim.fs.relpath('/var', '/usr/bin') -- nil +--- ``` +--- +--- @param base string +--- @param target string +--- @param opts table? Reserved for future use +--- @return string|nil +function M.relpath(base, target, opts) + vim.validate('base', base, 'string') + vim.validate('target', target, 'string') + vim.validate('opts', opts, 'table', true) + + base = vim.fs.normalize(vim.fs.abspath(base)) + target = vim.fs.normalize(vim.fs.abspath(target)) + if base == target then + return '.' + end + + local prefix = '' + if iswin then + prefix, base = split_windows_path(base) + end + base = prefix .. base .. (base ~= '/' and '/' or '') + + return vim.startswith(target, base) and target:sub(#base + 1) or nil +end + return M diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 6dc902489f..ee376f3a11 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -11,18 +11,7 @@ --- < --- Plugin authors are encouraged to write new healthchecks. |health-dev| --- ---- *g:health* ---- g:health This global variable controls the behavior and appearance of the ---- `health` floating window. It should be a dictionary containing the ---- following optional keys: ---- - `style`: string? Determines the display style of the `health` window. ---- Set to `'float'` to enable a floating window. Other ---- styles are not currently supported. ---- ---- Example: >lua ---- vim.g.health = { style = 'float' } ---- ---- Commands *health-commands* +--- COMMANDS *health-commands* --- --- *:che* *:checkhealth* --- :che[ckhealth] Run all healthchecks. @@ -50,6 +39,23 @@ --- :checkhealth vim* --- < --- +--- USAGE *health-usage* +--- +--- Local mappings in the healthcheck buffer: +--- +--- q Closes the window. +--- +--- Global configuration: +--- +--- *g:health* +--- g:health Dictionary with the following optional keys: +--- - `style` (`'float'|nil`) Set to "float" to display :checkhealth in +--- a floating window instead of the default behavior. +--- +--- Example: >lua +--- vim.g.health = { style = 'float' } +--- +--- -------------------------------------------------------------------------------- --- Create a healthcheck *health-dev* --- --- Healthchecks are functions that check the user environment, configuration, or diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5b92926a21..23f4e104d0 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1389,7 +1389,7 @@ end --- |LspAttach| autocommand. Example: --- --- ```lua ---- vim.api.nvim_create_autocommand('LspAttach', { +--- vim.api.nvim_create_autocmd('LspAttach', { --- callback = function(args) --- local client = vim.lsp.get_client_by_id(args.data.client_id) --- if client:supports_method('textDocument/foldingRange') then diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 5d11312c77..a99363d3d6 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -702,14 +702,14 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err --- --- @param ... string List to write to the buffer local function err_message(...) - local message = table.concat(vim.iter({ ... }):flatten():totable()) + local chunks = { { table.concat({ ... }) } } if vim.in_fast_event() then vim.schedule(function() - api.nvim_err_writeln(message) + vim.api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end) else - api.nvim_err_writeln(message) + vim.api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 9a879d9f38..8c1f3f10d4 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -208,7 +208,7 @@ end --- @param uri string --- @param client_id? integer ---- @param diagnostics vim.Diagnostic[] +--- @param diagnostics lsp.Diagnostic[] --- @param is_pull boolean local function handle_diagnostics(uri, client_id, diagnostics, is_pull) local fname = vim.uri_to_fname(uri) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 1945040bda..3779c342e8 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -582,9 +582,8 @@ NSC['window/showMessage'] = function(_, params, ctx) if message_type == protocol.MessageType.Error then err_message('LSP[', client_name, '] ', message) else - --- @type string - local message_type_name = protocol.MessageType[message_type] - api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message)) + message = ('LSP[%s][%s] %s\n'):format(client_name, protocol.MessageType[message_type], message) + api.nvim_echo({ { message } }, true, { err = true }) end return params end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5cccb3321f..14633adf0c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -879,8 +879,7 @@ function M.make_floating_popup_options(width, height, opts) col = col + (opts.offset_x or 0), height = height, focusable = opts.focusable, - relative = opts.relative == 'mouse' and 'mouse' - or opts.relative == 'editor' and 'editor' + relative = (opts.relative == 'mouse' or opts.relative == 'editor') and opts.relative or 'cursor', style = 'minimal', width = width, @@ -1433,7 +1432,7 @@ function M._make_floating_popup_size(contents, opts) if vim.tbl_isempty(line_widths) then for _, line in ipairs(contents) do local line_width = vim.fn.strdisplaywidth(line:gsub('%z', '\n')) - height = height + math.ceil(line_width / wrap_at) + height = height + math.max(1, math.ceil(line_width / wrap_at)) end else for i = 1, #contents do diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 24c3f243e5..02b12490af 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -959,7 +959,7 @@ do --- function vim.startswith(s, prefix) --- vim.validate('s', s, 'string') --- vim.validate('prefix', prefix, 'string') - --- ... + --- -- ... --- end --- ``` --- @@ -979,7 +979,7 @@ do --- age={age, 'number'}, --- hobbies={hobbies, 'table'}, --- } - --- ... + --- -- ... --- end --- ``` --- diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 89dc4e289a..0269699dfd 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -61,8 +61,6 @@ function M._create_parser(bufnr, lang, opts) { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } ) - self:parse() - return self end @@ -397,6 +395,8 @@ end --- Note: By default, disables regex syntax highlighting, which may be required for some plugins. --- In this case, add `vim.bo.syntax = 'on'` after the call to `start`. --- +--- Note: By default, the highlighter parses code asynchronously, using a segment time of 3ms. +--- --- Example: --- --- ```lua @@ -408,8 +408,8 @@ end --- }) --- ``` --- ----@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer) ----@param lang (string|nil) Language of the parser (default: from buffer filetype) +---@param bufnr integer? Buffer to be highlighted (default: current buffer) +---@param lang string? Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = vim._resolve_bufnr(bufnr) local parser = assert(M.get_parser(bufnr, lang, { error = false })) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d16013eca2..2777241e9f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -69,8 +69,8 @@ end ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? 0-indexed, exclusive ----@param parse_injections? boolean -local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) +---@param callback function? +local function compute_folds_levels(bufnr, info, srow, erow, callback) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) @@ -79,104 +79,112 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) return end - parser:parse(parse_injections and { srow, erow } or nil) - - local enter_counts = {} ---@type table<integer, integer> - local leave_counts = {} ---@type table<integer, integer> - local prev_start = -1 - local prev_stop = -1 - - parser:for_each_tree(function(tree, ltree) - local query = ts.query.get(ltree:lang(), 'folds') - if not query then + parser:parse(nil, function(_, trees) + if not trees then return end - -- Collect folds starting from srow - 1, because we should first subtract the folds that end at - -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do - for id, nodes in pairs(match) do - if query.captures[id] == 'fold' then - local range = ts.get_range(nodes[1], bufnr, metadata[id]) - local start, _, stop, stop_col = Range.unpack4(range) - - if #nodes > 1 then - -- assumes nodes are ordered by range - local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) - local _, _, end_stop, end_stop_col = Range.unpack4(end_range) - stop = end_stop - stop_col = end_stop_col - end + local enter_counts = {} ---@type table<integer, integer> + local leave_counts = {} ---@type table<integer, integer> + local prev_start = -1 + local prev_stop = -1 - if stop_col == 0 then - stop = stop - 1 - end + parser:for_each_tree(function(tree, ltree) + local query = ts.query.get(ltree:lang(), 'folds') + if not query then + return + end - local fold_length = stop - start + 1 - - -- Fold only multiline nodes that are not exactly the same as previously met folds - -- Checking against just the previously found fold is sufficient if nodes - -- are returned in preorder or postorder when traversing tree - if - fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) - then - enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 - leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 - prev_start = start - prev_stop = stop + -- Collect folds starting from srow - 1, because we should first subtract the folds that end at + -- srow - 1 from the level of srow - 1 to get accurate level of srow. + for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do + for id, nodes in pairs(match) do + if query.captures[id] == 'fold' then + local range = ts.get_range(nodes[1], bufnr, metadata[id]) + local start, _, stop, stop_col = Range.unpack4(range) + + if #nodes > 1 then + -- assumes nodes are ordered by range + local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) + local _, _, end_stop, end_stop_col = Range.unpack4(end_range) + stop = end_stop + stop_col = end_stop_col + end + + if stop_col == 0 then + stop = stop - 1 + end + + local fold_length = stop - start + 1 + + -- Fold only multiline nodes that are not exactly the same as previously met folds + -- Checking against just the previously found fold is sufficient if nodes + -- are returned in preorder or postorder when traversing tree + if + fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) + then + enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 + leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 + prev_start = start + prev_stop = stop + end end end end - end - end) + end) - local nestmax = vim.wo.foldnestmax - local level0_prev = info.levels0[srow] or 0 - local leave_prev = leave_counts[srow] or 0 - - -- We now have the list of fold opening and closing, fill the gaps and mark where fold start - for lnum = srow + 1, erow do - local enter_line = enter_counts[lnum] or 0 - local leave_line = leave_counts[lnum] or 0 - local level0 = level0_prev - leave_prev + enter_line - - -- Determine if it's the start/end of a fold - -- NB: vim's fold-expr interface does not have a mechanism to indicate that - -- two (or more) folds start at this line, so it cannot distinguish between - -- ( \n ( \n )) \n (( \n ) \n ) - -- versus - -- ( \n ( \n ) \n ( \n ) \n ) - -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and - -- vim interprets as the second case. - -- If it did have such a mechanism, (clamped - clamped_prev) - -- would be the correct number of starts to pass on. - local adjusted = level0 ---@type integer - local prefix = '' - if enter_line > 0 then - prefix = '>' - if leave_line > 0 then - -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line - -- so that f2 gets the correct level on this line. This may reduce the size of f1 below - -- foldminlines, but we don't handle it for simplicity. - adjusted = level0 - leave_line - leave_line = 0 + local nestmax = vim.wo.foldnestmax + local level0_prev = info.levels0[srow] or 0 + local leave_prev = leave_counts[srow] or 0 + + -- We now have the list of fold opening and closing, fill the gaps and mark where fold start + for lnum = srow + 1, erow do + local enter_line = enter_counts[lnum] or 0 + local leave_line = leave_counts[lnum] or 0 + local level0 = level0_prev - leave_prev + enter_line + + -- Determine if it's the start/end of a fold + -- NB: vim's fold-expr interface does not have a mechanism to indicate that + -- two (or more) folds start at this line, so it cannot distinguish between + -- ( \n ( \n )) \n (( \n ) \n ) + -- versus + -- ( \n ( \n ) \n ( \n ) \n ) + -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and + -- vim interprets as the second case. + -- If it did have such a mechanism, (clamped - clamped_prev) + -- would be the correct number of starts to pass on. + local adjusted = level0 ---@type integer + local prefix = '' + if enter_line > 0 then + prefix = '>' + if leave_line > 0 then + -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line + -- so that f2 gets the correct level on this line. This may reduce the size of f1 below + -- foldminlines, but we don't handle it for simplicity. + adjusted = level0 - leave_line + leave_line = 0 + end end - end - -- Clamp at foldnestmax. - local clamped = adjusted - if adjusted > nestmax then - prefix = '' - clamped = nestmax - end + -- Clamp at foldnestmax. + local clamped = adjusted + if adjusted > nestmax then + prefix = '' + clamped = nestmax + end - -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels(). - info.levels0[lnum] = adjusted - info.levels[lnum] = prefix .. tostring(clamped) + -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels(). + info.levels0[lnum] = adjusted + info.levels[lnum] = prefix .. tostring(clamped) - leave_prev = leave_line - level0_prev = adjusted - end + leave_prev = leave_line + level0_prev = adjusted + end + + if callback then + callback() + end + end) end local M = {} @@ -267,6 +275,8 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) schedule_if_loaded(bufnr, function() local srow_upd, erow_upd ---@type integer?, integer? local max_erow = api.nvim_buf_line_count(bufnr) + -- TODO(ribru17): Replace this with a proper .all() awaiter once #19624 is resolved + local iterations = 0 for _, change in ipairs(tree_changes) do local srow, _, erow, ecol = Range.unpack4(change) -- If a parser doesn't have any ranges explicitly set, treesitter will @@ -280,12 +290,14 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) end -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. srow = math.max(srow - vim.wo.foldminlines, 0) - compute_folds_levels(bufnr, foldinfo, srow, erow) srow_upd = srow_upd and math.min(srow_upd, srow) or srow erow_upd = erow_upd and math.max(erow_upd, erow) or erow - end - if #tree_changes > 0 then - foldinfo:foldupdate(bufnr, srow_upd, erow_upd) + compute_folds_levels(bufnr, foldinfo, srow, erow, function() + iterations = iterations + 1 + if iterations == #tree_changes then + foldinfo:foldupdate(bufnr, srow_upd, erow_upd) + end + end) end end) end @@ -343,8 +355,9 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, foldinfo.on_bytes_range = nil -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. srow = math.max(srow - vim.wo.foldminlines, 0) - compute_folds_levels(bufnr, foldinfo, srow, erow) - foldinfo:foldupdate(bufnr, srow, erow) + compute_folds_levels(bufnr, foldinfo, srow, erow, function() + foldinfo:foldupdate(bufnr, srow, erow) + end) end) end end @@ -401,9 +414,10 @@ api.nvim_create_autocmd('OptionSet', { for _, bufnr in ipairs(bufs) do foldinfos[bufnr] = FoldInfo.new(bufnr) api.nvim_buf_call(bufnr, function() - compute_folds_levels(bufnr, foldinfos[bufnr]) + compute_folds_levels(bufnr, foldinfos[bufnr], nil, nil, function() + foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) + end) end) - foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) end end, }) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 96503c38ea..be138885d5 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -69,6 +69,7 @@ end ---@field private _queries table<string,vim.treesitter.highlighter.Query> ---@field tree vim.treesitter.LanguageTree ---@field private redraw_count integer +---@field parsing boolean true if we are parsing asynchronously local TSHighlighter = { active = {}, } @@ -147,8 +148,6 @@ function TSHighlighter.new(tree, opts) vim.opt_local.spelloptions:append('noplainbuffer') end) - self.tree:parse() - return self end @@ -384,19 +383,23 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) end ---@private ----@param _win integer ---@param buf integer ---@param topline integer ---@param botline integer -function TSHighlighter._on_win(_, _win, buf, topline, botline) +function TSHighlighter._on_win(_, _, buf, topline, botline) local self = TSHighlighter.active[buf] - if not self then + if not self or self.parsing then return false end - self.tree:parse({ topline, botline + 1 }) - self:prepare_highlight_states(topline, botline + 1) + self.parsing = self.tree:parse({ topline, botline + 1 }, function(_, trees) + if trees and self.parsing then + self.parsing = false + api.nvim__redraw({ buf = buf, valid = false, flush = false }) + end + end) == nil self.redraw_count = self.redraw_count + 1 - return true + self:prepare_highlight_states(topline, botline) + return #self._highlight_states > 0 end api.nvim_set_decoration_provider(ns, { diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 446051dfd7..238a078703 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -133,8 +133,9 @@ function M.add(lang, opts) path = paths[1] end - return loadparser(path, lang, symbol_name) or nil, - string.format('Cannot load parser %s for language "%s"', path, lang) + local res = loadparser(path, lang, symbol_name) + return res, + res == nil and string.format('Cannot load parser %s for language "%s"', path, lang) or nil end --- @param x string|string[] diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 330eb45749..945a2301a9 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -44,6 +44,8 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') +local default_parse_timeout_ms = 3 + ---@alias TSCallbackName ---| 'changedtree' ---| 'bytes' @@ -76,6 +78,10 @@ local TSCallbackNames = { ---@field private _injections_processed boolean ---@field private _opts table Options ---@field private _parser TSParser Parser for language +---Table of regions for which the tree is currently running an async parse +---@field private _ranges_being_parsed table<string, boolean> +---Table of callback queues, keyed by each region for which the callbacks should be run +---@field private _cb_queues table<string, fun(err?: string, trees?: table<integer, TSTree>)[]> ---@field private _has_regions boolean ---@field private _regions table<integer, Range6[]>? ---List of regions this tree should manage and parse. If nil then regions are @@ -130,6 +136,8 @@ function LanguageTree.new(source, lang, opts) _injections_processed = false, _valid = false, _parser = vim._create_ts_parser(lang), + _ranges_being_parsed = {}, + _cb_queues = {}, _callbacks = {}, _callbacks_rec = {}, } @@ -232,6 +240,7 @@ end ---@param reload boolean|nil function LanguageTree:invalidate(reload) self._valid = false + self._parser:reset() -- buffer was reloaded, reparse all trees if reload then @@ -334,10 +343,12 @@ end --- @private --- @param range boolean|Range? +--- @param timeout integer? --- @return Range6[] changes --- @return integer no_regions_parsed --- @return number total_parse_time -function LanguageTree:_parse_regions(range) +--- @return boolean finished whether async parsing still needs time +function LanguageTree:_parse_regions(range, timeout) local changes = {} local no_regions_parsed = 0 local total_parse_time = 0 @@ -357,9 +368,14 @@ function LanguageTree:_parse_regions(range) ) then self._parser:set_included_ranges(ranges) + self._parser:set_timeout(timeout and timeout * 1000 or 0) -- ms -> micros local parse_time, tree, tree_changes = tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + if not tree then + return changes, no_regions_parsed, total_parse_time, false + end + -- Pass ranges if this is an initial parse local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) @@ -373,7 +389,7 @@ function LanguageTree:_parse_regions(range) end end - return changes, no_regions_parsed, total_parse_time + return changes, no_regions_parsed, total_parse_time, true end --- @private @@ -409,6 +425,82 @@ function LanguageTree:_add_injections() return query_time end +--- @param range boolean|Range? +--- @return string +local function range_to_string(range) + return type(range) == 'table' and table.concat(range, ',') or tostring(range) +end + +--- @private +--- @param range boolean|Range? +--- @param callback fun(err?: string, trees?: table<integer, TSTree>) +function LanguageTree:_push_async_callback(range, callback) + local key = range_to_string(range) + self._cb_queues[key] = self._cb_queues[key] or {} + local queue = self._cb_queues[key] + queue[#queue + 1] = callback +end + +--- @private +--- @param range boolean|Range? +--- @param err? string +--- @param trees? table<integer, TSTree> +function LanguageTree:_run_async_callbacks(range, err, trees) + local key = range_to_string(range) + for _, cb in ipairs(self._cb_queues[key]) do + cb(err, trees) + end + self._ranges_being_parsed[key] = false + self._cb_queues[key] = {} +end + +--- Run an asynchronous parse, calling {on_parse} when complete. +--- +--- @private +--- @param range boolean|Range? +--- @param on_parse fun(err?: string, trees?: table<integer, TSTree>) +--- @return table<integer, TSTree>? trees the list of parsed trees, if parsing completed synchronously +function LanguageTree:_async_parse(range, on_parse) + self:_push_async_callback(range, on_parse) + + -- If we are already running an async parse, just queue the callback. + local range_string = range_to_string(range) + if not self._ranges_being_parsed[range_string] then + self._ranges_being_parsed[range_string] = true + else + return + end + + local buf = vim.b[self._source] + local ct = buf.changedtick + local total_parse_time = 0 + local redrawtime = vim.o.redrawtime + local timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + + local function step() + -- If buffer was changed in the middle of parsing, reset parse state + if buf.changedtick ~= ct then + ct = buf.changedtick + total_parse_time = 0 + end + + local parse_time, trees, finished = tcall(self._parse, self, range, timeout) + total_parse_time = total_parse_time + parse_time + + if finished then + self:_run_async_callbacks(range, nil, trees) + return trees + elseif total_parse_time > redrawtime then + self:_run_async_callbacks(range, 'TIMEOUT', nil) + return nil + else + vim.schedule(step) + end + end + + return step() +end + --- Recursively parse all regions in the language tree using |treesitter-parsers| --- for the corresponding languages and run injection queries on the parsed trees --- to determine whether child trees should be created and parsed. @@ -420,11 +512,33 @@ 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 table<integer, TSTree> -function LanguageTree:parse(range) +--- @param on_parse fun(err?: string, trees?: table<integer, TSTree>)? Function invoked when parsing completes. +--- When provided and `vim.g._ts_force_sync_parsing` is not set, parsing will run +--- asynchronously. The first argument to the function is a string respresenting the error type, +--- in case of a failure (currently only possible for timeouts). The second argument is the list +--- of trees returned by the parse (upon success), or `nil` if the parse timed out (determined +--- by 'redrawtime'). +--- +--- If parsing was still able to finish synchronously (within 3ms), `parse()` returns the list +--- of trees. Otherwise, it returns `nil`. +--- @return table<integer, TSTree>? +function LanguageTree:parse(range, on_parse) + if on_parse then + return self:_async_parse(range, on_parse) + end + local trees, _ = self:_parse(range) + return trees +end + +--- @private +--- @param range boolean|Range|nil +--- @param timeout integer? +--- @return table<integer, TSTree> trees +--- @return boolean finished +function LanguageTree:_parse(range, timeout) if self:is_valid() then self:_log('valid') - return self._trees + return self._trees, true end local changes --- @type Range6[]? @@ -433,10 +547,15 @@ function LanguageTree:parse(range) local no_regions_parsed = 0 local query_time = 0 local total_parse_time = 0 + local is_finished --- @type boolean -- At least 1 region is invalid if not self:is_valid(true) then - changes, no_regions_parsed, total_parse_time = self:_parse_regions(range) + changes, no_regions_parsed, total_parse_time, is_finished = self:_parse_regions(range, timeout) + timeout = timeout and math.max(timeout - total_parse_time, 0) + if not is_finished then + return self._trees, is_finished + end -- Need to run injections when we parsed something if no_regions_parsed > 0 then self._injections_processed = false @@ -457,10 +576,17 @@ function LanguageTree:parse(range) }) for _, child in pairs(self._children) do - child:parse(range) + if timeout == 0 then + return self._trees, false + end + local ctime, _, child_finished = tcall(child._parse, child, range, timeout) + timeout = timeout and math.max(timeout - ctime, 0) + if not child_finished then + return self._trees, child_finished + end end - return self._trees + return self._trees, true end --- Invokes the callback for each |LanguageTree| recursively. @@ -907,6 +1033,7 @@ function LanguageTree:_edit( ) end + self._parser:reset() self._regions = nil local changed_range = { diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 1fc001b39f..66ab0d52f0 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -262,6 +262,7 @@ local explicit_queries = setmetatable({}, { ---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). function M.set(lang, query_name, text) + M.get:clear(lang, query_name) explicit_queries[lang][query_name] = M.parse(lang, text) end @@ -284,7 +285,15 @@ M.get = memoize('concat-2', function(lang, query_name) end return M.parse(lang, query_string) -end) +end, false) + +api.nvim_create_autocmd('OptionSet', { + pattern = { 'runtimepath' }, + group = api.nvim_create_augroup('ts_query_cache_reset', { clear = true }), + callback = function() + M.get:clear() + end, +}) --- Parses a {query} string and returns a `Query` object (|lua-treesitter-query|), which can be used --- to search the tree for the query patterns (via |Query:iter_captures()|, |Query:iter_matches()|), @@ -292,7 +301,7 @@ end) --- - `captures`: a list of unique capture names defined in the query (alias: `info.captures`). --- - `info.patterns`: information about predicates. --- ---- Example (select the code then run `:'<,'>lua` to try it): +--- Example (to try it, use `yxx` or select the code then run `:'<,'>lua`): --- ```lua --- local query = vim.treesitter.query.parse('vimdoc', [[ --- ; query @@ -316,7 +325,7 @@ M.parse = memoize('concat-2', function(lang, query) assert(language.add(lang)) local ts_query = vim._ts_parse_query(lang, query) return Query.new(lang, ts_query) -end) +end, false) --- Implementations of predicates that can optionally be prefixed with "any-". --- @@ -904,8 +913,8 @@ end ---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. --- ----@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch): ---- capture id, capture node, metadata, match +---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree): +--- capture id, capture node, metadata, match, tree --- ---@note Captures are only returned if the query pattern of a specific capture contained predicates. function Query:iter_captures(node, source, start, stop) @@ -915,6 +924,8 @@ function Query:iter_captures(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) + -- Copy the tree to ensure it is valid during the entire lifetime of the iterator + local tree = node:tree():copy() local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) -- For faster checks that a match is not in the cache. @@ -961,7 +972,7 @@ function Query:iter_captures(node, source, start, stop) match_cache[match_id] = metadata end - return capture, captured_node, metadata, match + return capture, captured_node, metadata, match, tree end return iter end @@ -983,7 +994,7 @@ end --- -- `node` was captured by the `name` capture in the match --- --- local node_data = metadata[id] -- Node level metadata ---- ... use the info here ... +--- -- ... use the info here ... --- end --- end --- end @@ -1002,7 +1013,7 @@ end --- (last) node instead of the full list of matching nodes. This option is only for backward --- compatibility and will be removed in a future release. --- ----@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata): pattern id, match, metadata +---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree): pattern id, match, metadata, tree function Query:iter_matches(node, source, start, stop, opts) opts = opts or {} opts.match_limit = opts.match_limit or 256 @@ -1013,6 +1024,8 @@ function Query:iter_matches(node, source, start, stop, opts) start, stop = value_or_node_range(start, stop, node) + -- Copy the tree to ensure it is valid during the entire lifetime of the iterator + local tree = node:tree():copy() local cursor = vim._create_ts_querycursor(node, self.query, start, stop, opts) local function iter() @@ -1050,7 +1063,7 @@ function Query:iter_matches(node, source, start, stop, opts) end -- TODO(lewis6991): create a new function that returns {match, metadata} - return pattern_i, captures, metadata + return pattern_i, captures, metadata, tree end return iter end diff --git a/runtime/syntax/lyrics.vim b/runtime/syntax/lyrics.vim index fd127988f2..48a5b1171c 100644 --- a/runtime/syntax/lyrics.vim +++ b/runtime/syntax/lyrics.vim @@ -2,7 +2,7 @@ " Language: LyRiCs " Maintainer: ObserverOfTime <chronobserver@disroot.org> " Filenames: *.lrc -" Last Change: 2024 Sep 20 +" Last Change: 2025 Jan 13 if exists('b:current_syntax') finish @@ -23,7 +23,7 @@ syn match lrcTagName contained nextgroup=lrcTagValue syn match lrcTagValue /:\zs.\+\ze\]/ contained " Lyrics -syn match lrcLyricTime /^\s*\(\[\d\d:\d\d\.\d\d\]\)\+/ +syn match lrcLyricTime /^\s*\(\[\d\d:\d\d\.\d\d\d\?\]\)\+/ \ contains=lrcNumber nextgroup=lrcLyricLine syn match lrcLyricLine /.*$/ contained contains=lrcWordTime,@Spell syn match lrcWordTime /<\d\d:\d\d\.\d\d>/ contained contains=lrcNumber,@NoSpell diff --git a/runtime/syntax/tiasm.vim b/runtime/syntax/tiasm.vim index bdadc4a0a7..c79596bdfe 100644 --- a/runtime/syntax/tiasm.vim +++ b/runtime/syntax/tiasm.vim @@ -99,4 +99,4 @@ hi def link tiasmIdentifier Identifier hi def link tiasmType Type hi def link tiasmFunction Function -let b:current_syntax = "lineartiasm" +let b:current_syntax = "tiasm" diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 12798201e2..edc69b907c 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -553,19 +553,21 @@ syn region vimPatSepZone oneline contained matchgroup=vimPatSepZ start="\\%\ syn region vimPatRegion contained transparent matchgroup=vimPatSepR start="\\[z%]\=(" end="\\)" contains=@vimSubstList oneline syn match vimNotPatSep contained "\\\\" syn cluster vimStringGroup contains=vimEscape,vimEscapeBrace,vimPatSep,vimNotPatSep,vimPatSepErr,vimPatSepZone,@Spell -syn region vimString oneline keepend start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend -syn region vimString oneline keepend start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ extend +syn region vimString oneline keepend matchgroup=vimString start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ contains=@vimStringGroup extend +syn region vimString oneline matchgroup=vimString start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ contains=vimQuoteEscape extend "syn region vimString oneline start="\s/\s*\A"lc=1 skip="\\\\\|\\+" end="/" contains=@vimStringGroup " see tst45.vim syn match vimString contained +"[^"]*\\$+ skipnl nextgroup=vimStringCont syn match vimStringCont contained +\(\\\\\|.\)\{-}[^\\]"+ + syn match vimEscape contained "\\." " syn match vimEscape contained +\\[befnrt\"]+ syn match vimEscape contained "\\\o\{1,3}\|\\[xX]\x\{1,2}\|\\u\x\{1,4}\|\\U\x\{1,8}" syn match vimEscape contained "\\<" contains=vimNotation syn match vimEscape contained "\\<\*[^>]*>\=>" +syn match vimQuoteEscape contained "''" -syn region vimString oneline start=+$'+ skip=+''+ end=+'+ contains=@vimStringInterpolation extend -syn region vimString oneline start=+$"+ end=+"+ contains=@vimStringGroup,@vimStringInterpolation extend +syn region vimString oneline matchgroup=vimString start=+$'+ skip=+''+ end=+'+ contains=vimQuoteEscape,@vimStringInterpolation extend +syn region vimString oneline matchgroup=vimString start=+$"+ end=+"+ contains=@vimStringGroup,@vimStringInterpolation extend syn region vimStringInterpolationExpr oneline contained matchgroup=vimSep start=+{+ end=+}+ contains=@vimExprList syn match vimStringInterpolationBrace contained "{{" syn match vimStringInterpolationBrace contained "}}" @@ -1399,6 +1401,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimPattern Type hi def link vimPlainMark vimMark hi def link vimPlainRegister vimRegister + hi def link vimQuoteEscape vimEscape hi def link vimRegister SpecialChar hi def link vimScriptDelim Comment hi def link vimSearchDelim Statement diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f888972f0d..58d3eeeadc 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -666,7 +666,16 @@ local function render_option_meta(_f, opt, write) write('--- ' .. l) end - write('--- @type ' .. OPTION_TYPES[opt.type]) + if opt.type == 'string' and not opt.list and opt.values then + local values = {} --- @type string[] + for _, e in ipairs(opt.values) do + values[#values + 1] = fmt("'%s'", e) + end + write('--- @type ' .. table.concat(values, '|')) + else + write('--- @type ' .. OPTION_TYPES[opt.type]) + end + write('vim.o.' .. opt.full_name .. ' = ' .. render_option_default(opt.defaults)) if opt.abbreviation then write('vim.o.' .. opt.abbreviation .. ' = vim.o.' .. opt.full_name) diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua index cbec50fc17..5d8bea6a98 100755 --- a/scripts/vimpatch.lua +++ b/scripts/vimpatch.lua @@ -1,7 +1,7 @@ -- Updates version.c list of applied Vim patches. -- -- Usage: --- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -V1 -es -i NONE +'luafile ./scripts/vimpatch.lua' +q +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -l ./scripts/vimpatch.lua local nvim = vim.api diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 767f5087c1..5b9946db39 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -49,7 +49,7 @@ if(ENABLE_LIBINTL) endif() if(ENABLE_WASMTIME) - find_package(Wasmtime 25.0.2 EXACT REQUIRED) + find_package(Wasmtime 25.0.3 EXACT REQUIRED) target_link_libraries(main_lib INTERFACE wasmtime) target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME) endif() diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d5eddb74de..b9e7d7143a 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,3 +1,5 @@ +// Island of misfit toys. + #include <stdbool.h> #include <stdint.h> #include <string.h> @@ -21,9 +23,11 @@ #include "nvim/lua/executor.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/pos_defs.h" +#include "nvim/strings.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -646,6 +650,10 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er }); OptIndex opt_idx = find_option(name.data); + VALIDATE_S(opt_idx != kOptInvalid, "option name", name.data, { + return (Object)OBJECT_INIT; + }); + OptVal value = NIL_OPTVAL; if (option_has_scope(opt_idx, scope)) { @@ -812,3 +820,100 @@ void nvim_unsubscribe(uint64_t channel_id, String event) { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } + +enum { LINE_BUFFER_MIN_SIZE = 4096, }; + +/// Writes a message to vim output or error buffer. The string is split +/// and flushed after each newline. Incomplete lines are kept for writing +/// later. +/// +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) +/// @param writeln Append a trailing newline +static void write_msg(String message, bool to_err, bool writeln) +{ + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; + StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; + +#define PUSH_CHAR(c) \ + if (kv_max(*line_buf) == 0) { \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (c == NL) { \ + kv_push(*line_buf, NUL); \ + if (to_err) { \ + emsg(line_buf->items); \ + } else { \ + msg(line_buf->items, 0); \ + } \ + if (msg_silent == 0) { \ + 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); \ + } + + no_wait_return++; + for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } + PUSH_CHAR(message.data[i]); + } + if (writeln) { + PUSH_CHAR(NL); + } + no_wait_return--; + msg_end(); +} + +/// @deprecated +/// +/// @param str Message +void nvim_out_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, false, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_writeln(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, true); +} + +/// @deprecated +/// +/// Use `nvim_echo` or `nvim_exec_lua("vim.notify(...)", ...)` instead. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) + FUNC_API_SINCE(7) FUNC_API_DEPRECATED_SINCE(13) +{ + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(msg)); + ADD_C(args, INTEGER_OBJ(log_level)); + ADD_C(args, DICT_OBJ(opts)); + + return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); +} diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 40e89d230d..664406ab6e 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -326,6 +326,7 @@ typedef struct { } Dict(cmd_opts); typedef struct { + Boolean err; Boolean verbose; } Dict(echo_opts); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 78aa7c00f7..c98635f8fd 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -776,7 +776,7 @@ char *api_typename(ObjectType t) UNREACHABLE; } -HlMessage parse_hl_msg(Array chunks, Error *err) +HlMessage parse_hl_msg(Array chunks, bool is_err, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; for (size_t i = 0; i < chunks.size; i++) { @@ -791,7 +791,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String str = copy_string(chunk.items[0].data.string, NULL); - int hl_id = 0; + int hl_id = is_err ? HLF_E : 0; if (chunk.size == 2) { hl_id = object_to_hl_id(chunk.items[1], "text highlight", err); } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e3e69f4ff6..faf6c0567c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -86,8 +86,6 @@ #include "nvim/vim_defs.h" #include "nvim/window.h" -#define LINE_BUFFER_MIN_SIZE 4096 - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" #endif @@ -518,26 +516,6 @@ Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err) return nlua_exec(code, args, kRetObject, arena, err); } -/// Notify the user with a message -/// -/// Relays the call to vim.notify . By default forwards your message in the -/// echo area but can be overridden to trigger desktop notifications. -/// -/// @param msg Message to display to the user -/// @param log_level The log level -/// @param opts Reserved for future use. -/// @param[out] err Error details, if any -Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) - FUNC_API_SINCE(7) -{ - MAXSIZE_TEMP_ARRAY(args, 3); - ADD_C(args, STRING_OBJ(msg)); - ADD_C(args, INTEGER_OBJ(log_level)); - ADD_C(args, DICT_OBJ(opts)); - - return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); -} - /// Calculates the number of display cells occupied by `text`. /// Control characters including [<Tab>] count as one cell. /// @@ -768,20 +746,24 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, NULL, err); } -/// Echo a message. +/// Prints a message given by a list of `[text, hl_group]` "chunks". /// -/// @param chunks A list of `[text, hl_group]` arrays, each representing a -/// text chunk with specified highlight group name or ID. -/// `hl_group` element can be omitted for no highlight. +/// Example: +/// ```lua +/// vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +/// ``` +/// +/// @param chunks List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +/// the (optional) name or ID `hl_group`. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. -/// - verbose: Message is printed as a result of 'verbose' option. -/// If Nvim was invoked with -V3log_file, the message will be -/// redirected to the log_file and suppressed from direct output. +/// - err: Treat the message like `:echoerr`. Sets `hl_group` to |hl-ErrorMsg| by default. +/// - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +/// will write the message to the "log" file instead of standard output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { - HlMessage hl_msg = parse_hl_msg(chunks, err); + HlMessage hl_msg = parse_hl_msg(chunks, opts->err, err); if (ERROR_SET(err)) { goto error; } @@ -790,7 +772,7 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, history ? "echomsg" : "echo", history); + msg_multihl(hl_msg, opts->err ? "echoerr" : history ? "echomsg" : "echo", history, opts->err); if (opts->verbose) { verbose_leave(); @@ -806,37 +788,6 @@ error: hl_msg_free(hl_msg); } -/// Writes a message to the Vim output buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_out_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, false, false); -} - -/// Writes a message to the Vim error buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_err_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, false); -} - -/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is -/// flushed (and displayed). -/// -/// @param str Message -/// @see nvim_err_write() -void nvim_err_writeln(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, true); -} - /// Gets the current list of buffer handles /// /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. @@ -1031,7 +982,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// in a virtual terminal having the intended size. /// /// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you -/// can use Nvim as a "scrollback pager" (for terminals like kitty): [terminal-scrollback-pager]() +/// can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +/// [terminal-scrollback-pager]() /// /// ```lua /// vim.api.nvim_create_user_command('TermHl', function() @@ -1531,20 +1483,17 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena) return rv; } -/// Self-identifies the client. +/// Self-identifies the client. Sets the `client` object returned by |nvim_get_chan_info()|. /// -/// The client/plugin/application should call this after connecting, to provide -/// hints about its identity and purpose, for debugging and orchestration. +/// Clients should call this just after connecting, to provide hints for debugging and +/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set +/// `name`.) /// -/// Can be called more than once; the caller should merge old info if -/// appropriate. Example: library first identifies the channel, then a plugin -/// using that library later identifies itself. -/// -/// @note "Something is better than nothing". You don't need to include all the -/// fields. +/// Can be called more than once; the caller should merge old info if appropriate. Example: library +/// first identifies the channel, then a plugin using that library later identifies itself. /// /// @param channel_id -/// @param name Short name for the connected client +/// @param name Client short-name. Sets the `client.name` field of |nvim_get_chan_info()|. /// @param version Dict describing the version, with these /// (optional) keys: /// - "major" major version (defaults to 0 if not set, for no release yet) @@ -1618,6 +1567,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// Gets information about a channel. /// +/// See |nvim_list_uis()| for an example of how to get channel info. +/// /// @param chan channel_id, or 0 for current channel /// @returns Channel info dict with these keys: /// - "id" Channel id. @@ -1635,8 +1586,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. /// for conpty on Windows). /// - "buffer" (optional) Buffer connected to |terminal| instance. -/// - "client" (optional) Info about the peer (client on the other end of the RPC channel), -/// which it provided via |nvim_set_client_info()|. +/// - "client" (optional) Info about the peer (client on the other end of the channel), as set +/// by |nvim_set_client_info()|. /// Dict nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err) FUNC_API_SINCE(4) @@ -1662,55 +1613,6 @@ Array nvim_list_chans(Arena *arena) return channel_all_info(arena); } -/// Writes a message to vim output or error buffer. The string is split -/// and flushed after each newline. Incomplete lines are kept for writing -/// later. -/// -/// @param message Message to write -/// @param to_err true: message is an error (uses `emsg` instead of `msg`) -/// @param writeln Append a trailing newline -static void write_msg(String message, bool to_err, bool writeln) -{ - static StringBuilder out_line_buf = KV_INITIAL_VALUE; - static StringBuilder err_line_buf = KV_INITIAL_VALUE; - StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; - -#define PUSH_CHAR(c) \ - if (kv_max(*line_buf) == 0) { \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } \ - if (c == NL) { \ - kv_push(*line_buf, NUL); \ - if (to_err) { \ - emsg(line_buf->items); \ - } else { \ - msg(line_buf->items, 0); \ - } \ - if (msg_silent == 0) { \ - 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); \ - } - - no_wait_return++; - for (uint32_t i = 0; i < message.size; i++) { - if (got_int) { - break; - } - PUSH_CHAR(message.data[i]); - } - if (writeln) { - PUSH_CHAR(NL); - } - no_wait_return--; - msg_end(); -} - // Functions used for testing purposes /// Returns object given as argument. @@ -1782,6 +1684,14 @@ Dict nvim__stats(Arena *arena) /// Gets a list of dictionaries representing attached UIs. /// +/// Example: The Nvim builtin |TUI| sets its channel info as described in |startup-tui|. In +/// particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +/// inspecting the client name of each UI: +/// +/// ```lua +/// vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +/// ``` +/// /// @return Array of UI dictionaries, each with these keys: /// - "height" Requested height of the UI /// - "width" Requested width of the UI diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 45c9e3f56c..225189a3f9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -101,10 +101,12 @@ /// @param config Map defining the window configuration. Keys: /// - relative: Sets the window layout to "floating", placed at (row,col) /// coordinates relative to: -/// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window. -/// - "cursor" Cursor position in current window. -/// - "mouse" Mouse position +/// - "cursor" Cursor position in current window. +/// - "editor" The global editor grid. +/// - "laststatus" 'laststatus' if present, or last row. +/// - "mouse" Mouse position. +/// - "tabline" Tabline if present, or first row. +/// - "win" Window given by the `win` field, or current window. /// - win: |window-ID| window to split, or relative window when creating a /// float (relative="win"). /// - anchor: Decides which corner of the float to place at (row,col): @@ -699,7 +701,9 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) FUNC_API_SINCE(6) { /// Keep in sync with FloatRelative in buffer_defs.h - static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; + static const char *const float_relative_str[] = { + "editor", "win", "cursor", "mouse", "tabline", "laststatus" + }; /// Keep in sync with WinSplit in buffer_defs.h static const char *const win_split_str[] = { "left", "right", "above", "below" }; @@ -805,6 +809,10 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeCursor; } else if (striequal(str, "mouse")) { *out = kFloatRelativeMouse; + } else if (striequal(str, "tabline")) { + *out = kFloatRelativeTabline; + } else if (striequal(str, "laststatus")) { + *out = kFloatRelativeLaststatus; } else { return false; } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index bb639edc07..361bb8db12 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -31,6 +31,7 @@ #include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/os/input.h" @@ -1096,6 +1097,10 @@ static void do_arg_all(int count, int forceit, int keep_tabs) tabpage_T *const new_lu_tp = curtab; + // Stop Visual mode, the cursor and "VIsual" may very well be invalid after + // switching to another buffer. + reset_VIsual_and_resel(); + // Try closing all windows that are not in the argument list. // Also close windows that are not full width; // When 'hidden' or "forceit" set the buffer becomes hidden. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3fe44beab9..bffb29578f 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -900,6 +900,8 @@ typedef enum { kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, kFloatRelativeMouse = 3, + kFloatRelativeTabline = 4, + kFloatRelativeLaststatus = 5, } FloatRelative; /// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a90f275713..5b91f1248f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7968,8 +7968,7 @@ void ex_execute(exarg_T *eap) } else if (eap->cmdidx == CMD_echoerr) { // We don't want to abort following commands, restore did_emsg. int save_did_emsg = did_emsg; - msg_ext_set_kind("echoerr"); - emsg_multiline(ga.ga_data, true); + emsg_multiline(ga.ga_data, "echoerr", HLF_E, true); if (!force_abort) { did_emsg = save_did_emsg; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4ce1960dcf..5c7b2791e8 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -9178,6 +9178,7 @@ M.funcs = { { 'timeout', 'integer' }, { 'skip', 'string|function' }, }, + returns = 'integer', signature = 'search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])', }, searchcount = { diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index b9b5a055fb..012d23b567 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -843,11 +843,10 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, goto theend; } - // Don't assume current and new values are of the same type in order to future-proof the code for - // when an option can have multiple types. - const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean) - && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean)); - const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString; + // Current value and new value must have the same type. + assert(curval.type == newval.type); + const bool is_num = curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean; + const bool is_string = curval.type == kOptValTypeString; if (op != NULL && *op != '=') { if (!hidden && is_num) { // number or bool @@ -1900,8 +1899,6 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off) /// /// @return Typval converted to OptVal. Must be freed by caller. /// Returns NIL_OPTVAL for invalid option name. -/// -/// TODO(famiu): Refactor this to support multitype options. static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, bool *error) { OptVal value = NIL_OPTVAL; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6f9f0f07c9..137226e2ad 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -982,7 +982,7 @@ void handle_did_throw(void) if (messages != NULL) { do { msglist_T *next = messages->next; - emsg_multiline(messages->msg, messages->multiline); + emsg_multiline(messages->msg, "emsg", HLF_E, messages->multiline); xfree(messages->msg); xfree(messages->sfile); xfree(messages); diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 79eea718f4..5b47afa4b2 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -140,8 +140,9 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool } if (invalid) { - row2 = mt_paired(key) ? marktree_get_altpos(buf->b_marktree, key, NULL).row : row; - buf_put_decor(buf, mt_decor(key), row, row2); + MTPos end = marktree_get_altpos(buf->b_marktree, key, itr); + mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; + buf_put_decor(buf, mt_decor(key), row, end.row); } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) { buf_signcols_count_range(buf, row1, row2, 0, kNone); } @@ -394,7 +395,8 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln bool invalidated = false; // Invalidate/delete mark if (!only_copy && !mt_invalid(mark) && mt_invalidate(mark) && !mt_end(mark)) { - MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); + MarkTreeIter enditr[1] = { *itr }; + MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, enditr); // Invalidate unpaired marks in deleted lines and paired marks whose entire // range has been deleted. if ((!mt_paired(mark) && mark.pos.row < u_row) @@ -409,6 +411,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln copy = true; invalidated = true; mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID; + mt_itr_rawkey(enditr).flags |= MT_FLAG_INVALID; marktree_revise_meta(buf->b_marktree, itr, mark); buf_decor_remove(buf, mark.pos.row, endpos.row, mark.pos.col, mt_decor(mark), false); } diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index c79683dc00..0298381ece 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -314,21 +314,6 @@ end --- @param o vim.option_meta --- @return string -local function get_type_flags(o) - local opt_types = (type(o.type) == 'table') and o.type or { o.type } - local type_flags = '0' - assert(type(opt_types) == 'table') - - for _, opt_type in ipairs(opt_types) do - assert(type(opt_type) == 'string') - type_flags = ('%s | (1 << %s)'):format(type_flags, opt_type_enum(opt_type)) - end - - return type_flags -end - ---- @param o vim.option_meta ---- @return string local function get_scope_flags(o) local scope_flags = '0' @@ -427,8 +412,8 @@ local function dump_option(i, o) if o.abbreviation then w(' .shortname=' .. cstr(o.abbreviation)) end + w(' .type=' .. opt_type_enum(o.type)) w(' .flags=' .. get_flags(o)) - w(' .type_flags=' .. get_type_flags(o)) w(' .scope_flags=' .. get_scope_flags(o)) w(' .scope_idx=' .. get_scope_idx(o)) if o.enable_if then diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 419c806592..22643457d6 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -966,7 +966,11 @@ static void ins_compl_insert_bytes(char *p, int len) /// -1 mean normal item. int ins_compl_col_range_attr(int col) { - if (col >= compl_col && col < compl_ins_end_col) { + if (get_cot_flags() & kOptCotFlagFuzzy) { + return -1; + } + + if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) { return syn_name2attr("ComplMatchIns"); } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 0a412b4ca9..68d3af6074 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -958,7 +958,7 @@ static void nlua_print_event(void **argv) HlMessage msg = KV_INITIAL_VALUE; HlMessageChunk chunk = { { .data = argv[0], .size = (size_t)(intptr_t)argv[1] - 1 }, 0 }; kv_push(msg, chunk); - msg_multihl(msg, "lua_print", true); + msg_multihl(msg, "lua_print", true, false); } /// Print as a Vim message diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 28ad2cf4d3..3493384a8f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -128,9 +128,9 @@ static const TSLanguage *load_language_from_object(lua_State *L, const char *pat { uv_lib_t lib; if (uv_dlopen(path, &lib)) { + xstrlcpy(IObuff, uv_dlerror(&lib), sizeof(IObuff)); uv_dlclose(&lib); - luaL_error(L, "Failed to load parser for language '%s': uv_dlopen: %s", - lang_name, uv_dlerror(&lib)); + luaL_error(L, "Failed to load parser for language '%s': uv_dlopen: %s", lang_name, IObuff); } char symbol_buf[128]; @@ -138,8 +138,9 @@ static const TSLanguage *load_language_from_object(lua_State *L, const char *pat TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + xstrlcpy(IObuff, uv_dlerror(&lib), sizeof(IObuff)); uv_dlclose(&lib); - luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + luaL_error(L, "Failed to load parser: uv_dlsym: %s", IObuff); } TSLanguage *lang = lang_parser(); @@ -489,7 +490,11 @@ static int parser_parse(lua_State *L) // Sometimes parsing fails (timeout, or wrong parser ABI) // In those case, just return an error. if (!new_tree) { - return luaL_error(L, "An error occurred when parsing."); + if (ts_parser_timeout_micros(p) == 0) { + // No timeout set, must have had an error + return luaL_error(L, "An error occurred when parsing."); + } + return 0; } // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC. diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index e308a81a5d..8d5ff2a8c1 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -74,3 +74,5 @@ typedef struct { } CharBoundsOff; typedef utf8proc_int32_t GraphemeState; + +enum { UNICODE_INVALID = 0xFFFD, }; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index ce04362a3e..fb7fdfb8b2 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1860,7 +1860,7 @@ int gchar_pos(pos_T *pos) FUNC_ATTR_NONNULL_ARG(1) { // When searching columns is sometimes put at the end of a line. - if (pos->col == MAXCOL) { + if (pos->col == MAXCOL || pos->col > ml_get_len(pos->lnum)) { return NUL; } return utf_ptr2char(ml_get_pos(pos)); diff --git a/src/nvim/message.c b/src/nvim/message.c index d45bc147cb..f87eba27d0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -293,20 +293,31 @@ void msg_multiline(String str, int hl_id, bool check_int, bool hist, bool *need_ } } -void msg_multihl(HlMessage hl_msg, const char *kind, bool history) +// Avoid starting a new message for each chunk and adding message to history in msg_keep(). +static bool is_multihl = false; + +void msg_multihl(HlMessage hl_msg, const char *kind, bool history, bool err) { no_wait_return++; msg_start(); msg_clr_eos(); bool need_clear = false; + msg_ext_history = history; msg_ext_set_kind(kind); + is_multihl = true; for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); - msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); + if (err) { + emsg_multiline(chunk.text.data, kind, chunk.hl_id, true); + } else { + msg_multiline(chunk.text, chunk.hl_id, true, false, &need_clear); + } + assert(!ui_has(kUIMessages) || msg_ext_kind == kind); } if (history && kv_size(hl_msg)) { add_msg_hist_multihl(NULL, 0, 0, true, hl_msg); } + is_multihl = false; no_wait_return--; msg_end(); } @@ -342,18 +353,19 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline) } entered++; - // Add message to history (unless it's a repeated kept message or a - // truncated message) - if (s != keep_msg - || (*s != '<' - && last_msg_hist != NULL - && last_msg_hist->msg != NULL - && strcmp(s, last_msg_hist->msg) != 0)) { + // Add message to history (unless it's a truncated, repeated kept or multihl message). + if ((s != keep_msg + || (*s != '<' + && last_msg_hist != NULL + && last_msg_hist->msg != NULL + && strcmp(s, last_msg_hist->msg) != 0)) && !is_multihl) { add_msg_hist(s, -1, hl_id, multiline); } + if (!is_multihl) { + msg_start(); + } // Truncate the message if needed. - msg_start(); char *buf = msg_strtrunc(s, false); if (buf != NULL) { s = buf; @@ -368,7 +380,10 @@ bool msg_keep(const char *s, int hl_id, bool keep, bool multiline) if (need_clear) { msg_clr_eos(); } - bool retval = msg_end(); + bool retval = true; + if (!is_multihl) { + retval = msg_end(); + } if (keep && retval && vim_strsize(s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg(s, 0); @@ -618,6 +633,9 @@ void msg_source(int hl_id) msg_scroll = true; // this will take more than one line msg(p, hl_id); xfree(p); + if (is_multihl) { + msg_start(); // avoided in msg_keep() but need the "msg_didout" newline here + } } p = get_emsg_lnum(); if (p != NULL) { @@ -652,7 +670,7 @@ int emsg_not_now(void) return false; } -bool emsg_multiline(const char *s, bool multiline) +bool emsg_multiline(const char *s, const char *kind, int hl_id, bool multiline) { bool ignore = false; @@ -750,14 +768,13 @@ bool emsg_multiline(const char *s, bool multiline) } emsg_on_display = true; // remember there is an error message - int hl_id = HLF_E; // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after } // wait_return() has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero if (msg_ext_kind == NULL) { - msg_ext_set_kind("emsg"); + msg_ext_set_kind(kind); } // Display name and line number for the source of the error. @@ -765,7 +782,7 @@ bool emsg_multiline(const char *s, bool multiline) msg_source(hl_id); if (msg_ext_kind == NULL) { - msg_ext_set_kind("emsg"); + msg_ext_set_kind(kind); } // Display the error message itself. @@ -781,7 +798,7 @@ bool emsg_multiline(const char *s, bool multiline) /// @return true if wait_return() not called bool emsg(const char *s) { - return emsg_multiline(s, false); + return emsg_multiline(s, "emsg", HLF_E, false); } void emsg_invreg(int name) @@ -821,7 +838,7 @@ bool semsg_multiline(const char *const fmt, ...) vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); va_end(ap); - ret = emsg_multiline(errbuf, true); + ret = emsg_multiline(errbuf, "emsg", HLF_E, true); return ret; } @@ -905,7 +922,7 @@ void msg_schedule_semsg(const char *const fmt, ...) static void msg_semsg_multiline_event(void **argv) { char *s = argv[0]; - emsg_multiline(s, true); + emsg_multiline(s, "emsg", HLF_E, true); xfree(s); } @@ -1196,7 +1213,7 @@ void ex_messages(exarg_T *eap) msg_hist_off = true; for (; p != NULL && !got_int; p = p->next) { if (kv_size(p->multihl)) { - msg_multihl(p->multihl, p->kind, false); + msg_multihl(p->multihl, p->kind, false, false); } else if (p->msg != NULL) { msg_keep(p->msg, p->hl_id, false, p->multiline); } @@ -3307,6 +3324,10 @@ int redirecting(void) || redir_reg || redir_vname || capture_ga != NULL; } +// Save and restore message kind when emitting a verbose message. +static const char *pre_verbose_kind = NULL; +static const char *verbose_kind = "verbose"; + /// Before giving verbose message. /// Must always be called paired with verbose_leave()! void verbose_enter(void) @@ -3314,6 +3335,10 @@ void verbose_enter(void) if (*p_vfile != NUL) { msg_silent++; } + if (msg_ext_kind != verbose_kind) { + pre_verbose_kind = msg_ext_kind; + msg_ext_set_kind("verbose"); + } } /// After giving verbose message. @@ -3325,14 +3350,17 @@ void verbose_leave(void) msg_silent = 0; } } + if (pre_verbose_kind != NULL) { + msg_ext_set_kind(pre_verbose_kind); + pre_verbose_kind = NULL; + } } /// Like verbose_enter() and set msg_scroll when displaying the message. void verbose_enter_scroll(void) { - if (*p_vfile != NUL) { - msg_silent++; - } else { + verbose_enter(); + if (*p_vfile == NUL) { // always scroll up, don't overwrite msg_scroll = true; } @@ -3341,11 +3369,8 @@ void verbose_enter_scroll(void) /// Like verbose_leave() and set cmdline_row when displaying the message. void verbose_leave_scroll(void) { - if (*p_vfile != NUL) { - if (--msg_silent < 0) { - msg_silent = 0; - } - } else { + verbose_leave(); + if (*p_vfile == NUL) { cmdline_row = msg_row; } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 476c7ee8a4..d51b4cc88b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4345,6 +4345,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T colnr_T endcol = MAXCOL; colnr_T cs, ce; char *p = ml_get(lnum); + int plen = ml_get_len(lnum); bdp->startspaces = 0; bdp->endspaces = 0; @@ -4394,7 +4395,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T bdp->textlen = endcol - startcol + inclusive; } bdp->textcol = startcol; - bdp->textstart = p + startcol; + bdp->textstart = startcol <= plen ? p + startcol : p; } /// Handle the add/subtract operator. diff --git a/src/nvim/option.c b/src/nvim/option.c index 551ea0be20..593ac62172 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3121,17 +3121,10 @@ bool optval_equal(OptVal o1, OptVal o2) UNREACHABLE; } -/// Get type of option. Does not support multitype options. +/// Get type of option. static OptValType option_get_type(const OptIndex opt_idx) { - assert(!option_is_multitype(opt_idx)); - - // If the option only supports a single type, it means that the index of the option's type flag - // corresponds to the value of the type enum. So get the index of the type flag using xctz() and - // use that as the option's type. - OptValType type = xctz(options[opt_idx].type_flags); - assert(type > kOptValTypeNil && type < kOptValTypeSize); - return type; + return options[opt_idx].type; } /// Create OptVal from var pointer. @@ -3149,11 +3142,6 @@ OptVal optval_from_varp(OptIndex opt_idx, void *varp) return BOOLEAN_OPTVAL(curbufIsChanged()); } - if (option_is_multitype(opt_idx)) { - // Multitype options are stored as OptVal. - return *(OptVal *)varp; - } - OptValType type = option_get_type(opt_idx); switch (type) { @@ -3264,33 +3252,6 @@ OptVal object_as_optval(Object o, bool *error) UNREACHABLE; } -/// Get an allocated string containing a list of valid types for an option. -/// For options with a singular type, it returns the name of the type. For options with multiple -/// possible types, it returns a slash separated list of types. For example, if an option can be a -/// number, boolean or string, the function returns "number/boolean/string" -static char *option_get_valid_types(OptIndex opt_idx) -{ - StringBuilder str = KV_INITIAL_VALUE; - kv_resize(str, 32); - - // Iterate through every valid option value type and check if the option supports that type - for (OptValType type = 0; type < kOptValTypeSize; type++) { - if (option_has_type(opt_idx, type)) { - const char *typename = optval_type_get_name(type); - - if (str.size == 0) { - kv_concat(str, typename); - } else { - kv_printf(str, "/%s", typename); - } - } - } - - // Ensure that the string is NUL-terminated. - kv_push(str, NUL); - return str.items; -} - /// Check if option is hidden. /// /// @param opt_idx Option index in options[] table. @@ -3303,25 +3264,10 @@ bool is_option_hidden(OptIndex opt_idx) && options[opt_idx].var == &options[opt_idx].def_val.data; } -/// Check if option is multitype (supports multiple types). -static bool option_is_multitype(OptIndex opt_idx) -{ - const OptTypeFlags type_flags = get_option(opt_idx)->type_flags; - assert(type_flags != 0); - return !is_power_of_two(type_flags); -} - /// Check if option supports a specific type. bool option_has_type(OptIndex opt_idx, OptValType type) { - // Ensure that type flags variable can hold all types. - STATIC_ASSERT(kOptValTypeSize <= sizeof(OptTypeFlags) * 8, - "Option type_flags cannot fit all option types"); - // Ensure that the type is valid before accessing type_flags. - assert(type > kOptValTypeNil && type < kOptValTypeSize); - // Bitshift 1 by the value of type to get the type's corresponding flag, and check if it's set in - // the type_flags bit field. - return get_option(opt_idx)->type_flags & (1 << type); + return options[opt_idx].type == type; } /// Check if option supports a specific scope. @@ -3658,11 +3604,10 @@ static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, } } else if (!option_has_type(opt_idx, newval->type)) { char *rep = optval_to_cstr(*newval); - char *valid_types = option_get_valid_types(opt_idx); + const char *type_str = optval_type_get_name(opt->type); snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), - opt->fullname, valid_types, optval_type_get_name(newval->type), rep); + opt->fullname, type_str, optval_type_get_name(newval->type), rep); xfree(rep); - xfree(valid_types); errmsg = errbuf; } else if (newval->type == kOptValTypeNumber) { // Validate and bound check num option values. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 832e03148a..2b51547004 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -54,9 +54,6 @@ typedef enum { kOptValTypeNumber, kOptValTypeString, } OptValType; -/// Always update this whenever a new option type is added. -#define kOptValTypeSize (kOptValTypeString + 1) -typedef uint32_t OptTypeFlags; /// Scopes that an option can support. typedef enum { @@ -99,7 +96,6 @@ typedef struct { int os_flags; /// Old value of the option. - /// TODO(famiu): Convert `os_oldval` and `os_newval` to `OptVal` to accommodate multitype options. OptValData os_oldval; /// New value of the option. OptValData os_newval; @@ -173,7 +169,7 @@ typedef struct { char *fullname; ///< full option name char *shortname; ///< permissible abbreviation uint32_t flags; ///< see above - OptTypeFlags type_flags; ///< option type flags, see OptValType + OptValType type; ///< option type OptScopeFlags scope_flags; ///< option scope flags, see OptScope void *var; ///< global option: pointer to variable; ///< window-local option: NULL; diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index f4d0a9a4b0..9975e7870f 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -20,7 +20,7 @@ "R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \ "]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \ "_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \ - "Z:StatusLineTermNC,g:MsgArea,0:Whitespace,I:NormalNC" + "Z:StatusLineTermNC,g:MsgArea,h:ComplMatchIns,0:Whitespace,I:NormalNC" // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 3142c30080..2425dcb93e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,7 +7,7 @@ --- @field alias? string|string[] --- @field short_desc? string|fun(): string --- @field varname? string ---- @field type vim.option_type|vim.option_type[] +--- @field type vim.option_type --- @field immutable? boolean --- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] @@ -834,7 +834,7 @@ return { abbreviation = 'bh', cb = 'did_set_bufhidden', defaults = { if_true = '' }, - values = { 'hide', 'unload', 'delete', 'wipe' }, + values = { '', 'hide', 'unload', 'delete', 'wipe' }, desc = [=[ This option specifies what happens when a buffer is no longer displayed in a window: @@ -888,11 +888,12 @@ return { cb = 'did_set_buftype', defaults = { if_true = '' }, values = { + '', + 'acwrite', + 'help', 'nofile', 'nowrite', 'quickfix', - 'help', - 'acwrite', 'terminal', 'prompt', }, @@ -1531,8 +1532,8 @@ return { "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are present, - "noselect" has precedence. + pre-selected. If both "noinsert" and "noselect" are + present, "noselect" has precedence. fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where @@ -1554,7 +1555,7 @@ return { abbreviation = 'csl', cb = 'did_set_completeslash', defaults = { if_true = '' }, - values = { 'slash', 'backslash' }, + values = { '', 'slash', 'backslash' }, desc = [=[ only modifiable in MS-Windows When this option is set it overrules 'shellslash' for completion: @@ -2017,8 +2018,10 @@ return { "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or 'indentexpr'. ]=], + -- TODO(lewis6991): bug, values currently cannot be combined expand_cb = 'expand_set_debug', full_name = 'debug', + list = 'comma', scope = { 'global' }, short_desc = N_('to "msg" to see all error messages'), type = 'string', @@ -4299,7 +4302,7 @@ return { abbreviation = 'icm', cb = 'did_set_inccommand', defaults = { if_true = 'nosplit' }, - values = { 'nosplit', 'split' }, + values = { 'nosplit', 'split', '' }, desc = [=[ When nonempty, shows the effects of |:substitute|, |:smagic|, |:snomagic| and user commands with the |:command-preview| flag as you @@ -5735,7 +5738,7 @@ return { abbreviation = 'mousem', cb = 'did_set_mousemodel', defaults = { if_true = 'popup_setpos' }, - values = { 'extend', 'popup', 'popup_setpos', 'mac' }, + values = { 'extend', 'popup', 'popup_setpos' }, desc = [=[ Sets the model to use for the mouse. The name mostly specifies what the right mouse button is used for: @@ -6520,8 +6523,8 @@ return { defaults = { if_true = 2000 }, desc = [=[ Time in milliseconds for redrawing the display. Applies to - 'hlsearch', 'inccommand', |:match| highlighting and syntax - highlighting. + 'hlsearch', 'inccommand', |:match| highlighting, syntax highlighting, + and async |LanguageTree:parse()|. When redrawing takes more than this many milliseconds no further matches will be highlighted. For syntax highlighting the time applies per window. When over the diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 9b7b50ae04..eac9ea02e0 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -395,7 +395,9 @@ static int expand_set_opt_string(optexpand_T *args, const char **values, size_t } for (const char **val = values; *val != NULL; val++) { - if (include_orig_val && *option_val != NUL) { + if (**val == NUL) { + continue; // Ignore empty + } else if (include_orig_val && *option_val != NUL) { if (strcmp(*val, option_val) == 0) { continue; } @@ -1091,7 +1093,7 @@ int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches /// The 'debug' option is changed. const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_debug, opt_debug_values, false); + return did_set_opt_strings(p_debug, opt_debug_values, true); } int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) @@ -2545,7 +2547,7 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char *val, const char **values, int list) +static int check_opt_strings(char *val, const char **values, bool list) { return opt_strings_flags(val, values, NULL, list); } @@ -2562,7 +2564,10 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla { unsigned new_flags = 0; - while (*val) { + // If not list and val is empty, then force one iteration of the while loop + bool iter_one = (*val == NUL) && !list; + + while (*val || iter_one) { for (unsigned i = 0;; i++) { if (values[i] == NULL) { // val not found in values[] return FAIL; @@ -2577,6 +2582,9 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla break; // check next item in val list } } + if (iter_one) { + break; + } } if (flagp != NULL) { *flagp = new_flags; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 2b1dd22b1a..d1c6f647fd 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -412,7 +412,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i /// Returns attributes for every cell, or NULL if all attributes are the same. static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr) { - if ((hlf != HLF_PSI && hlf != HLF_PNI) + if (*text == NUL || (hlf != HLF_PSI && hlf != HLF_PNI) || (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { return NULL; @@ -654,89 +654,87 @@ void pum_redraw(void) s = p; } int w = ptr2cells(p); + if (*p != NUL && *p != TAB && totwidth + w <= pum_width) { + width += w; + continue; + } - if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) { - // Display the text that fits or comes before a Tab. - // First convert it to printable characters. - char *st; - char saved = *p; + // Display the text that fits or comes before a Tab. + // First convert it to printable characters. + char saved = *p; - if (saved != NUL) { - *p = NUL; - } - st = transstr(s, true); - if (saved != NUL) { - *p = saved; - } - - int *attrs = NULL; - if (item_type == CPT_ABBR) { - attrs = pum_compute_text_attrs(st, hlf, pum_array[idx].pum_user_abbr_hlattr); - } + if (saved != NUL) { + *p = NUL; + } + char *st = transstr(s, true); + if (saved != NUL) { + *p = saved; + } - if (pum_rl) { - char *rt = reverse_text(st); - char *rt_start = rt; - int cells = vim_strsize(rt); - - if (cells > pum_width) { - do { - cells -= utf_ptr2cells(rt); - MB_PTR_ADV(rt); - } while (cells > pum_width); - - if (cells < pum_width) { - // Most left character requires 2-cells but only 1 cell - // is available on screen. Put a '<' on the left of the - // pum item - *(--rt) = '<'; - cells++; - } - } + int *attrs = NULL; + if (item_type == CPT_ABBR) { + attrs = pum_compute_text_attrs(st, hlf, + pum_array[idx].pum_user_abbr_hlattr); + } - if (attrs == NULL) { - grid_line_puts(grid_col - cells + 1, rt, -1, attr); - } else { - pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs); + if (pum_rl) { + char *rt = reverse_text(st); + char *rt_start = rt; + int cells = vim_strsize(rt); + + if (cells > pum_width) { + do { + cells -= utf_ptr2cells(rt); + MB_PTR_ADV(rt); + } while (cells > pum_width); + + if (cells < pum_width) { + // Most left character requires 2-cells but only 1 cell is available on + // screen. Put a '<' on the left of the pum item. + *(--rt) = '<'; + cells++; } + } - xfree(rt_start); - xfree(st); - grid_col -= width; + if (attrs == NULL) { + grid_line_puts(grid_col - cells + 1, rt, -1, attr); } else { - if (attrs == NULL) { - grid_line_puts(grid_col, st, -1, attr); - } else { - pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); - } - - xfree(st); - grid_col += width; + pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs); } - if (attrs != NULL) { - XFREE_CLEAR(attrs); + xfree(rt_start); + xfree(st); + grid_col -= width; + } else { + if (attrs == NULL) { + grid_line_puts(grid_col, st, -1, attr); + } else { + pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); } - if (*p != TAB) { - break; - } + xfree(st); + grid_col += width; + } - // Display two spaces for a Tab. - if (pum_rl) { - grid_line_puts(grid_col - 1, " ", 2, attr); - grid_col -= 2; - } else { - grid_line_puts(grid_col, " ", 2, attr); - grid_col += 2; - } - totwidth += 2; - // start text at next char - s = NULL; - width = 0; + if (attrs != NULL) { + XFREE_CLEAR(attrs); + } + + if (*p != TAB) { + break; + } + + // Display two spaces for a Tab. + if (pum_rl) { + grid_line_puts(grid_col - 1, " ", 2, attr); + grid_col -= 2; } else { - width += w; + grid_line_puts(grid_col, " ", 2, attr); + grid_col += 2; } + totwidth += 2; + s = NULL; // start text at next char + width = 0; } } diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c index 8c4a91e736..eabde2f9f7 100644 --- a/src/nvim/tui/termkey/termkey.c +++ b/src/nvim/tui/termkey/termkey.c @@ -634,40 +634,13 @@ static void eat_bytes(TermKey *tk, size_t count) tk->buffcount -= count; } -// TODO(dundargoc): we should be able to replace this with utf_char2bytes from mbyte.c int fill_utf8(int codepoint, char *str) { - int nbytes = utf_char2len(codepoint); - + int nbytes = utf_char2bytes(codepoint, str); str[nbytes] = 0; - - // This is easier done backwards - int b = nbytes; - while (b > 1) { - b--; - str[b] = (char)0x80 | (codepoint & 0x3f); - codepoint >>= 6; - } - - switch (nbytes) { - case 1: - str[0] = (codepoint & 0x7f); break; - case 2: - str[0] = (char)0xc0 | (codepoint & 0x1f); break; - case 3: - str[0] = (char)0xe0 | (codepoint & 0x0f); break; - case 4: - str[0] = (char)0xf0 | (codepoint & 0x07); break; - case 5: - str[0] = (char)0xf8 | (codepoint & 0x03); break; - case 6: - str[0] = (char)0xfc | (codepoint & 0x01); break; - } - return nbytes; } -#define UTF8_INVALID 0xFFFD static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, size_t *nbytep) { unsigned nbytes; @@ -681,7 +654,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, return TERMKEY_RES_KEY; } else if (b0 < 0xc0) { // Starts with a continuation byte - that's not right - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = 1; return TERMKEY_RES_KEY; } else if (b0 < 0xe0) { @@ -700,7 +673,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, nbytes = 6; *cp = b0 & 0x01; } else { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = 1; return TERMKEY_RES_KEY; } @@ -714,7 +687,7 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, cb = bytes[b]; if (cb < 0x80 || cb >= 0xc0) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; *nbytep = b; return TERMKEY_RES_KEY; } @@ -725,14 +698,14 @@ static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, // Check for overlong sequences if ((int)nbytes > utf_char2len(*cp)) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; } // Check for UTF-16 surrogates or invalid *cps if ((*cp >= 0xD800 && *cp <= 0xDFFF) || *cp == 0xFFFE || *cp == 0xFFFF) { - *cp = UTF8_INVALID; + *cp = UNICODE_INVALID; } *nbytep = nbytes; @@ -962,9 +935,9 @@ static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, siz if (res == TERMKEY_RES_AGAIN && force) { // There weren't enough bytes for a complete UTF-8 sequence but caller // demands an answer. About the best thing we can do here is eat as many - // bytes as we have, and emit a UTF8_INVALID. If the remaining bytes + // bytes as we have, and emit a UNICODE_INVALID. If the remaining bytes // arrive later, they'll be invalid too. - codepoint = UTF8_INVALID; + codepoint = UNICODE_INVALID; *nbytep = tk->buffcount; res = TERMKEY_RES_KEY; } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d242baf83b..51815c36e1 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -223,10 +223,10 @@ void ui_refresh(void) // Reset 'cmdheight' for all tabpages when ext_messages toggles. if (had_message != ui_ext[kUIMessages]) { set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0); - command_height(); FOR_ALL_TABS(tp) { tp->tp_ch_used = had_message; } + msg_scroll_flush(); } if (!ui_active()) { diff --git a/src/nvim/vterm/encoding.c b/src/nvim/vterm/encoding.c index cc3208cfa2..f9061e8e50 100644 --- a/src/nvim/vterm/encoding.c +++ b/src/nvim/vterm/encoding.c @@ -210,6 +210,7 @@ static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi } } +// https://en.wikipedia.org/wiki/DEC_Special_Graphics static const struct StaticTableEncoding encoding_DECdrawing = { { .decode = &decode_table }, { @@ -247,13 +248,6 @@ static const struct StaticTableEncoding encoding_DECdrawing = { } }; -static const struct StaticTableEncoding encoding_uk = { - { .decode = &decode_table }, - { - [0x23] = 0x00a3, // £ - } -}; - static struct { VTermEncodingType type; char designation; @@ -262,7 +256,6 @@ static struct { encodings[] = { { ENC_UTF8, 'u', &encoding_utf8 }, { ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing }, - { ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk }, { ENC_SINGLE_94, 'B', &encoding_usascii }, { 0 }, }; diff --git a/src/nvim/window.c b/src/nvim/window.c index 8c8df72590..1c0d8c1027 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -835,6 +835,10 @@ void ui_ext_win_position(win_T *wp, bool validate) col += tcol - 1; } } + } else if (c.relative == kFloatRelativeLaststatus) { + row += Rows - (int)p_ch - last_stl_height(false); + } else if (c.relative == kFloatRelativeTabline) { + row += tabline_height(); } bool resort = wp->w_grid_alloc.comp_index != 0 @@ -1066,6 +1070,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl return NULL; } need_status = STATUS_HEIGHT; + win_float_anchor_laststatus(); } bool do_equal = false; @@ -6357,7 +6362,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) fr = curfr; // put fr at window that grows } - // If not enough room thn move as far as we can + // If not enough room then move as far as we can offset = MIN(offset, room); // No room at all, quit. @@ -6803,6 +6808,7 @@ void last_status(bool morewin) { // Don't make a difference between horizontal or vertical split. last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0); + win_float_anchor_laststatus(); } // Remove status line from window, replacing it with a horizontal separator if needed. diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 3e791e2beb..d11b965dfc 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -307,6 +307,15 @@ void win_check_anchored_floats(win_T *win) } } +void win_float_anchor_laststatus(void) +{ + FOR_ALL_WINDOWS_IN_TAB(win, curtab) { + if (win->w_config.relative == kFloatRelativeLaststatus) { + win->w_pos_changed = true; + } + } +} + void win_reconfig_floats(void) { for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { diff --git a/test/functional/api/deprecated_spec.lua b/test/functional/api/deprecated_spec.lua new file mode 100644 index 0000000000..2efcfda873 --- /dev/null +++ b/test/functional/api/deprecated_spec.lua @@ -0,0 +1,21 @@ +-- Island of misfit toys. +--- @diagnostic disable: deprecated + +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +describe('deprecated', function() + before_each(n.clear) + + describe('nvim_notify', function() + it('can notify a info message', function() + n.api.nvim_notify('hello world', 2, {}) + end) + + it('can be overridden', function() + n.command('lua vim.notify = function(...) return 42 end') + t.eq(42, n.api.nvim_exec_lua("return vim.notify('Hello world')", {})) + n.api.nvim_notify('hello world', 4, {}) + end) + end) +end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 6a94881093..c8d6110207 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1794,6 +1794,16 @@ describe('API/extmarks', function() eq({}, get_extmark_by_id(ns, 4, {})) end) + it('no crash checking invalided flag of sign pair end key #31856', function() + api.nvim_buf_set_lines(0, 0, 1, false, { '', '' }) + api.nvim_set_option_value('signcolumn', 'auto:2', {}) + set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 }) + set_extmark(ns, 2, 1, 0, { sign_text = 'S2', end_row = 1 }) + command('d') + api.nvim_buf_clear_namespace(0, ns, 0, -1) + n.assert_alive() + end) + it('can set a URL', function() local url1 = 'https://example.com' local url2 = 'http://127.0.0.1' diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 578fa361e8..044213d83a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -781,18 +781,6 @@ describe('API', function() end) end) - describe('nvim_notify', function() - it('can notify a info message', function() - api.nvim_notify('hello world', 2, {}) - end) - - it('can be overridden', function() - command('lua vim.notify = function(...) return 42 end') - eq(42, api.nvim_exec_lua("return vim.notify('Hello world')", {})) - api.nvim_notify('hello world', 4, {}) - end) - end) - describe('nvim_input', function() it('Vimscript error: does NOT fail, updates v:errmsg', function() local status, _ = pcall(api.nvim_input, ':call bogus_fn()<CR>') @@ -3680,6 +3668,30 @@ describe('API', function() async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) eq('', exec_capture('messages')) end) + + it('can print error message', function() + async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true }) + screen:expect([[ + | + {1:~ }|*3 + {3: }| + {9:Error} | + {9:Message} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed(':messages<CR>') + screen:expect([[ + ^ | + {1:~ }|*6 + | + ]]) + async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true }) + screen:expect([[ + ^ | + {1:~ }|*6 + {9:Error}{16:Message} | + ]]) + end) end) describe('nvim_open_term', function() diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 218f9bbc46..0ba0948eee 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -168,8 +168,8 @@ describe('vim.fs', function() local function run(dir, depth, skip) return exec_lua(function() - local r = {} - local skip_f + local r = {} --- @type table<string, string> + local skip_f --- @type function if skip then skip_f = function(n0) if vim.tbl_contains(skip or {}, n0) then @@ -340,9 +340,6 @@ describe('vim.fs', function() end) describe('normalize()', function() - -- local function vim.fs.normalize(path, opts) - -- return exec_lua([[return vim.fs.vim.fs.normalize(...)]], path, opts) - -- end it('removes trailing /', function() eq('/home/user', vim.fs.normalize('/home/user/')) end) @@ -389,7 +386,7 @@ describe('vim.fs', function() eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts)) end) - it('does not change case on paths, see #31833', function() + it('always treats paths as case-sensitive #31833', function() eq('TEST', vim.fs.normalize('TEST', win_opts)) eq('test', vim.fs.normalize('test', win_opts)) eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts)) @@ -496,8 +493,8 @@ describe('vim.fs', function() end) describe('abspath()', function() - local cwd = is_os('win') and vim.uv.cwd():gsub('\\', '/') or vim.uv.cwd() - local home = is_os('win') and vim.uv.os_homedir():gsub('\\', '/') or vim.uv.os_homedir() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local home = t.fix_slashes(assert(vim.uv.os_homedir())) it('works', function() eq(cwd .. '/foo', vim.fs.abspath('foo')) @@ -529,4 +526,57 @@ describe('vim.fs', function() end) end end) + + describe('relpath()', function() + it('works', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local my_dir = vim.fs.joinpath(cwd, 'foo') + + eq(nil, vim.fs.relpath('/var/lib', '/var')) + eq(nil, vim.fs.relpath('/var/lib', '/bin')) + eq(nil, vim.fs.relpath(my_dir, 'bin')) + eq(nil, vim.fs.relpath(my_dir, './bin')) + eq(nil, vim.fs.relpath(my_dir, '././')) + eq(nil, vim.fs.relpath(my_dir, '../')) + eq(nil, vim.fs.relpath('/var/lib', '/')) + eq(nil, vim.fs.relpath('/var/lib', '//')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq('.', vim.fs.relpath('/var/lib', '/var/lib')) + eq('lib', vim.fs.relpath('/var/', '/var/lib')) + eq('var/lib', vim.fs.relpath('/', '/var/lib')) + eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json')) + eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar')) + eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar'))) + eq('bar', vim.fs.relpath('foo', 'foo/bar')) + eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo')) + + if is_os('win') then + eq(nil, vim.fs.relpath('/', ' ')) + eq(nil, vim.fs.relpath('/', 'var')) + else + local cwd_rel_root = cwd:sub(2) + eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' ')) + eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var')) + end + + if is_os('win') then + eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo')) + eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa')) + eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc')) + eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb')) + eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json')) + eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('a/b/c', 'a\\b')) + eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d')) + eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json')) + end + end) + end) end) diff --git a/test/functional/func/memoize_spec.lua b/test/functional/lua/func_memoize_spec.lua index ca518ab88d..ca518ab88d 100644 --- a/test/functional/func/memoize_spec.lua +++ b/test/functional/lua/func_memoize_spec.lua diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9e75861aa0..55e5158596 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3435,7 +3435,6 @@ stack traceback: end) it('can discard input', function() - clear() -- discard every other normal 'x' command exec_lua [[ n_key = 0 @@ -3461,7 +3460,6 @@ stack traceback: end) it('callback invalid return', function() - clear() -- second key produces an error which removes the callback exec_lua [[ n_call = 0 diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 9cefe96e79..5e9766c784 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3501,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index 9f7fdf529f..ac58df4bba 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -802,9 +802,10 @@ t2]]) end) it('can detect a new parser and refresh folds accordingly', function() - write_file('test_fold_file.txt', test_text) + local name = t.tmpname() + write_file(name, test_text) + command('edit ' .. name) command [[ - e test_fold_file.txt set filetype=some_filetype_without_treesitter_parser set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 ]] @@ -818,7 +819,7 @@ t2]]) -- reload buffer as c filetype to simulate new parser being found feed('GA// vim: ft=c<Esc>') - command([[w | e]]) + command([[write | edit]]) eq({ [1] = '>1', diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 120a15d7f9..a93b1063a1 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -117,6 +117,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 1, 3, 1, 3 }) return tostring(tree:root()) end) @@ -133,6 +134,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 10, 10, 10, 10 }) return tostring(tree:root()) end) @@ -149,6 +151,7 @@ describe('treesitter language API', function() '<node primitive_type>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local node = langtree:named_node_for_range({ 1, 3, 1, 3 }) return tostring(node) end) @@ -160,6 +163,7 @@ describe('treesitter language API', function() exec_lua(function() _G.langtree = vim.treesitter.get_parser(0, 'lua') + _G.langtree:parse() _G.node = _G.langtree:node_for_range({ 0, 3, 0, 3 }) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index c87a56b160..9839022c5e 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -20,6 +20,7 @@ describe('treesitter node API', function() insert('F') exec_lua(function() vim.treesitter.start(0, 'lua') + vim.treesitter.get_parser(0):parse() vim.treesitter.get_node():tree() vim.treesitter.get_node():tree() collectgarbage() @@ -45,6 +46,7 @@ describe('treesitter node API', function() -- this buffer doesn't have filetype set! insert('local foo = function() end') exec_lua(function() + vim.treesitter.get_parser(0, 'lua'):parse() _G.node = vim.treesitter.get_node({ bufnr = 0, pos = { 0, 6 }, -- on "foo" diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 2f80cee226..825c2b8f29 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -10,6 +10,7 @@ local exec_lua = n.exec_lua local pcall_err = t.pcall_err local feed = n.feed local run_query = ts_t.run_query +local assert_alive = n.assert_alive describe('treesitter parser API', function() before_each(function() @@ -90,6 +91,197 @@ describe('treesitter parser API', function() eq(true, exec_lua('return parser:parse()[1] == tree2')) end) + it('parses buffer asynchronously', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.lang = vim.treesitter.language.inspect('c') + _G.parser:parse(nil, function(_, trees) + _G.tree = trees[1] + _G.root = _G.tree:root() + end) + vim.wait(100, function() end) + end) + + eq('<tree>', exec_lua('return tostring(tree)')) + eq('<node translation_unit>', exec_lua('return tostring(root)')) + eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}')) + + eq(1, exec_lua('return root:child_count()')) + exec_lua('child = root:child(0)') + eq('<node function_definition>', exec_lua('return tostring(child)')) + eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}')) + + eq('function_definition', exec_lua('return child:type()')) + eq(true, exec_lua('return child:named()')) + eq('number', type(exec_lua('return child:symbol()'))) + eq(true, exec_lua('return lang.symbols[child:type()]')) + + exec_lua('anon = root:descendant_for_range(0,8,0,9)') + eq('(', exec_lua('return anon:type()')) + eq(false, exec_lua('return anon:named()')) + eq('number', type(exec_lua('return anon:symbol()'))) + eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=])) + + exec_lua('descendant = root:descendant_for_range(1,2,1,12)') + eq('<node declaration>', exec_lua('return tostring(descendant)')) + eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}')) + eq( + '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))', + exec_lua('return descendant:sexpr()') + ) + + feed('2G7|ay') + exec_lua(function() + _G.parser:parse(nil, function(_, trees) + _G.tree2 = trees[1] + _G.root2 = _G.tree2:root() + _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13) + end) + vim.wait(100, function() end) + end) + eq(false, exec_lua('return tree2 == tree1')) + eq(false, exec_lua('return root2 == root')) + eq('<node declaration>', exec_lua('return tostring(descendant2)')) + eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}')) + + eq(true, exec_lua('return child == child')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child == root:child(0)')) + eq(false, exec_lua('return child == descendant2')) + eq(false, exec_lua('return child == nil')) + eq(false, exec_lua('return child == tree')) + + eq('string', exec_lua('return type(child:id())')) + eq(true, exec_lua('return child:id() == child:id()')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child:id() == root:child(0):id()')) + eq(false, exec_lua('return child:id() == descendant2:id()')) + eq(false, exec_lua('return child:id() == nil')) + eq(false, exec_lua('return child:id() == tree')) + + -- unchanged buffer: return the same tree + eq(true, exec_lua('return parser:parse()[1] == tree2')) + end) + + it('does not crash when editing large files', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + while not _G.done do + -- Busy wait until async parsing has completed + vim.wait(100, function() end) + end + end) + + eq(true, exec_lua([[return done]])) + exec_lua(function() + vim.api.nvim_input('Lxj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + assert_alive() + end) + + it('resets parsing state on tree changes', function() + insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]]) + feed('yy1000p') + + exec_lua(function() + vim.cmd('set ft=lua') + + vim.treesitter.start(0) + local parser = assert(vim.treesitter.get_parser(0)) + + parser:parse(true, function() end) + vim.api.nvim_buf_set_lines(0, 1, -1, false, {}) + parser:parse(true) + end) + end) + + it('resets when buffer was editing during an async parse', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + feed('gg4jO// Comment<Esc>') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + end) + + exec_lua(function() + vim.api.nvim_input('ggdj') + end) + + eq(false, exec_lua([[return done]])) + exec_lua(function() + while not _G.done do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq(true, exec_lua([[return done]])) + eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]])) + eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]])) + end) + + it('handles multiple async parse calls', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + -- Spy on vim.schedule + local schedule = vim.schedule + vim.schedule = function(fn) + _G.schedules = _G.schedules + 1 + schedule(fn) + end + _G.schedules = 0 + _G.parser = vim.treesitter.get_parser(0, 'c') + for i = 1, 5 do + _G['done' .. i] = false + _G.parser:parse(nil, function() + _G['done' .. i] = true + end) + end + schedule(function() + _G.schedules_snapshot = _G.schedules + end) + end) + + eq(2, exec_lua([[return schedules_snapshot]])) + eq( + { false, false, false, false, false }, + exec_lua([[return { done1, done2, done3, done4, done5 }]]) + ) + exec_lua(function() + while not _G.done1 do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]])) + end) + local test_text = [[ void ui_refresh(void) { @@ -950,11 +1142,13 @@ print() feed(':set ft=help<cr>') exec_lua(function() - vim.treesitter.get_parser(0, 'vimdoc', { - injections = { - vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', - }, - }) + vim.treesitter + .get_parser(0, 'vimdoc', { + injections = { + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, + }) + :parse() end) end) diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 6e21ed1d99..6bab171ee8 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -86,7 +86,7 @@ void ui_refresh(void) local before = vim.api.nvim__stats().ts_query_parse_count collectgarbage('stop') for _ = 1, _n, 1 do - vim.treesitter.query.parse('c', long_query, _n) + vim.treesitter.query.parse('c', long_query) end collectgarbage('restart') collectgarbage('collect') @@ -96,8 +96,39 @@ void ui_refresh(void) end eq(1, q(1)) - -- cache is cleared by garbage collection even if valid "cquery" reference is kept around - eq(1, q(100)) + -- cache is retained even after garbage collection + eq(0, q(100)) + end) + + it('cache is cleared upon runtimepath changes, or setting query manually', function() + ---@return number + exec_lua(function() + _G.query_parse_count = _G.query_parse_count or 0 + local parse = vim.treesitter.query.parse + vim.treesitter.query.parse = function(...) + _G.query_parse_count = _G.query_parse_count + 1 + return parse(...) + end + end) + + local function q(_n) + return exec_lua(function() + for _ = 1, _n, 1 do + vim.treesitter.query.get('c', 'highlights') + end + return _G.query_parse_count + end) + end + + eq(1, q(10)) + exec_lua(function() + vim.opt.rtp:prepend('/another/dir') + end) + eq(2, q(100)) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', [[; test]]) + end) + eq(3, q(100)) end) it('supports query and iter by capture (iter_captures)', function() diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index be0a9b80cf..ca26c46fc5 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1012,6 +1012,97 @@ describe('float window', function() end) end) + it('placed relative to tabline and laststatus', function() + local screen = Screen.new(20, 10) + screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.Magenta } }) + command('set showtabline=1 laststatus=1') + api.nvim_open_win(0, false, { + relative = 'laststatus', + border = 'single', + anchor = 'SE', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + local tabwin = api.nvim_open_win(0, false, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + screen:expect([[ + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*3 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabnew | tabnext') + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('vsplit') + screen:expect([[ + {5: }{100:4}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }{2:│}{1:~}| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {3:[No Name] }{2:<}| + | + ]]) + command('quit') + api.nvim_win_set_config(tabwin, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 1, + col = 0, + }) + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabonly') + screen:expect([[ + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + end) + local function with_ext_multigrid(multigrid) local screen, attrs before_each(function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 77ffc475b0..ea4edefe8a 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -315,6 +315,71 @@ describe('ui/ext_messages', function() }) feed('<Esc>') command('set showmode') + + -- kind=echoerr for nvim_echo() err + feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })<CR>') + screen:expect({ + cmdline = { { + abort = false, + } }, + messages = { + { + content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, + history = true, + kind = 'echoerr', + }, + }, + }) + + feed(':1verbose filter Diff[AC] hi<CR>') + screen:expect({ + cmdline = { { + abort = false, + } }, + messages = { + { + content = { + { '\nDiffAdd ' }, + { 'xxx', 22, 30 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '81 ' }, + { 'guibg=', 18, 5 }, + { 'LightBlue' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { + { '\nDiffChange ' }, + { 'xxx', 4, 31 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '225 ' }, + { 'guibg=', 18, 5 }, + { 'LightMagenta' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) end) it(':echoerr', function() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 60d59190ce..5e883d1a92 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5415,6 +5415,45 @@ describe('builtin popupmenu', function() feed('<C-E><Esc>') end) + -- oldtest: Test_pum_highlights_match_with_abbr() + it('can highlight matched text with abbr', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + feed('i<C-X><C-O>') + screen:expect([[ + ^ | + {s:foobar ! }{1: }| + {n:foobaz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + feed('foo') + screen:expect([[ + foo^ | + {ms:foo}{s:bar ! }{1: }| + {mn:foo}{n:baz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + + feed('<C-E><Esc>') + end) + -- oldtest: Test_pum_user_abbr_hlgroup() it('custom abbr_hlgroup override', function() exec([[ @@ -5853,6 +5892,48 @@ describe('builtin popupmenu', function() {2:-- INSERT --} | ]]) feed('<Esc>') + + -- Does not highlight the compl leader + command('set cot+=menuone,noselect') + feed('S<C-X><C-O>') + local pum_start = [[ + {10:^ }| + {n:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]] + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:f}{9:oo}{10:^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + + command('set cot+=fuzzy') + feed('S<C-X><C-O>') + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:foo^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + command('set cot-=fuzzy') + feed('Sf<C-N>') + screen:expect([[ + {10:f^ }| + {1:~ }|*18 + {2:-- }{6:Pattern not found} | + ]]) + feed('<C-E><Esc>') end) end end diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 3802ee3fc9..bcb0f3d4c4 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -145,8 +145,8 @@ let test_values = { \ 'winwidth': [[1, 10, 999], [-1, 0]], \ "\ string options - \ 'ambiwidth': [['', 'single', 'double'], ['xxx']], - \ 'background': [['', 'light', 'dark'], ['xxx']], + \ 'ambiwidth': [['single', 'double'], ['xxx']], + \ 'background': [['light', 'dark'], ['xxx']], "\ 'backspace': [[0, 1, 2, 3, '', 'indent', 'eol', 'start', 'nostop', "\ " 'eol,start', 'indent,eol,nostop'], "\ " [-1, 4, 'xxx']], @@ -214,12 +214,12 @@ let test_values = { \ ['xxx', 'foldcolumn:xxx', 'algorithm:xxx', 'algorithm:']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], - \ 'eadirection': [['', 'both', 'ver', 'hor'], ['xxx', 'ver,hor']], + \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], "\ 'encoding': [['latin1'], ['xxx', '']], \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'], \ ['xxx']], \ 'fileencoding': [['', 'latin1', 'xxx'], []], - \ 'fileformat': [['', 'dos', 'unix', 'mac'], ['xxx']], + \ 'fileformat': [['dos', 'unix', 'mac'], ['xxx']], \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']], \ 'fillchars': [['', 'stl:x', 'stlnc:x', 'vert:x', 'fold:x', 'foldopen:x', \ 'foldclose:x', 'foldsep:x', 'diff:x', 'eob:x', 'lastline:x', @@ -274,7 +274,7 @@ let test_values = { \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']], \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'], \ ['xxx', 'n,v,i']], - \ 'mousemodel': [['', 'extend', 'popup', 'popup_setpos'], ['xxx']], + \ 'mousemodel': [['extend', 'popup', 'popup_setpos'], ['xxx']], \ 'mouseshape': [['', 'n:arrow'], ['xxx']], \ 'nrformats': [['', 'alpha', 'octal', 'hex', 'bin', 'unsigned', 'blank', \ 'alpha,hex,bin'], @@ -299,7 +299,7 @@ let test_values = { \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', \ 'help,options,slash'], \ ['xxx', 'curdir,sesdir']], - \ 'showcmdloc': [['', 'last', 'statusline', 'tabline'], ['xxx']], + \ 'showcmdloc': [['last', 'statusline', 'tabline'], ['xxx']], "\ 'signcolumn': [['', 'auto', 'no', 'yes', 'number'], ['xxx', 'no,yes']], \ 'spellfile': [['', 'file.en.add', 'xxx.en.add,yyy.gb.add,zzz.ja.add', \ '/tmp/dir\ with\ space/en.utf-8.add', @@ -311,7 +311,7 @@ let test_values = { \ 'spellsuggest': [['', 'best', 'double', 'fast', '100', 'timeout:100', \ 'timeout:-1', 'file:/tmp/file', 'expr:Func()', 'double,33'], \ ['xxx', '-1', 'timeout:', 'best,double', 'double,fast']], - \ 'splitkeep': [['', 'cursor', 'screen', 'topline'], ['xxx']], + \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']], \ 'statusline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']], "\ 'swapsync': [['', 'sync', 'fsync'], ['xxx']], \ 'switchbuf': [['', 'useopen', 'usetab', 'split', 'vsplit', 'newtab', diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index d890884eb5..2c6b1bd0f4 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -308,7 +308,7 @@ func s:GetFilenameChecks() abort \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'), \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'), - \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore'], + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore', '.rgignore', '.ignore', '.dockerignore', '.npmignore', '.vscodeignore'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], @@ -395,7 +395,7 @@ func s:GetFilenameChecks() abort \ 'jq': ['file.jq'], \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'], \ 'json5': ['file.json5'], - \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock'], + \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock', expand("$HOME/.config/VSCodium/User/settings.json")], \ 'jsonl': ['file.jsonl'], \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'], \ 'jsp': ['file.jsp'], diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index d090186b62..dfa140b163 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -504,7 +504,8 @@ func Test_set_completion_string_values() call assert_equal('current', getcompletion('set browsedir=', 'cmdline')[1]) endif call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1]) - call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + "call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + call assert_equal('help', getcompletion('set buftype=', 'cmdline')[1]) call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1]) if exists('+clipboard') " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1]) diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index e902ea3bc2..e4abf978ab 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1519,6 +1519,39 @@ func Test_pum_highlights_match() call StopVimInTerminal(buf) endfunc +func Test_pum_highlights_match_with_abbr() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "foo") + call VerifyScreenDump(buf, 'Test_pum_highlights_19', {}) + + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call StopVimInTerminal(buf) +endfunc + func Test_pum_user_abbr_hlgroup() CheckScreendump let lines =<< trim END @@ -1817,6 +1850,27 @@ func Test_pum_matchins_highlight_combine() call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {}) call term_sendkeys(buf, "\<Esc>") + " Does not highlight the compl leader + call TermWait(buf) + call term_sendkeys(buf, ":set cot+=menuone,noselect\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>f\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_07', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call term_sendkeys(buf, ":set cot+=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>f\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_08', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call term_sendkeys(buf, ":set cot-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "Sf\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_09', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index d4a3dc3301..4d54d29df7 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -282,7 +282,7 @@ func Test_ruby_Vim_buffer_get() call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name')) call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name')) call assert_fails('ruby print Vim::Buffer[3].name', - \ "NoMethodError: undefined method `name' for nil") + \ "NoMethodError") %bwipe endfunc @@ -364,7 +364,7 @@ func Test_ruby_Vim_evaluate_dict() redir => l:out ruby d = Vim.evaluate("d"); print d redir END - call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n")) + call assert_equal(['{"a"=>"foo","b"=>123}'], split(substitute(l:out, '\s', '', 'g'), "\n")) endfunc " Test Vim::message({msg}) (display message {msg}) @@ -384,7 +384,7 @@ func Test_ruby_print() call assert_equal('1.23', RubyPrint('1.23')) call assert_equal('Hello World!', RubyPrint('"Hello World!"')) call assert_equal('[1, 2]', RubyPrint('[1, 2]')) - call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})')) + call assert_equal('{"k1"=>"v1","k2"=>"v2"}', substitute(RubyPrint('({"k1" => "v1", "k2" => "v2"})'), '\s', '', 'g')) call assert_equal('true', RubyPrint('true')) call assert_equal('false', RubyPrint('false')) call assert_equal('', RubyPrint('nil')) diff --git a/test/old/testdir/test_stacktrace.vim b/test/old/testdir/test_stacktrace.vim index 1e47deefdd..9e1f51e1f6 100644 --- a/test/old/testdir/test_stacktrace.vim +++ b/test/old/testdir/test_stacktrace.vim @@ -10,7 +10,7 @@ func Filepath(name) endfunc func AssertStacktrace(expect, actual) - call assert_equal(#{lnum: 581, filepath: Filepath('runtest.vim')}, a:actual[0]) + call assert_equal(Filepath('runtest.vim'), a:actual[0]['filepath']) call assert_equal(a:expect, a:actual[-len(a:expect):]) endfunc @@ -97,6 +97,12 @@ func Test_vstacktrace() call Xfunc1() catch let stacktrace = v:stacktrace + try + call Xfunc1() + catch + let stacktrace_inner = v:stacktrace + endtry + let stacktrace_after = v:stacktrace " should be restored by the exception stack to the previous one endtry call assert_equal([], v:stacktrace) call AssertStacktrace([ @@ -104,9 +110,15 @@ func Test_vstacktrace() \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], stacktrace) + call AssertStacktrace([ + \ #{funcref: funcref('Test_vstacktrace'), lnum: 101, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], stacktrace_inner) + call assert_equal(stacktrace, stacktrace_after) endfunc -func Test_zzz_stacktrace_vim9() +func Test_stacktrace_vim9() let lines =<< trim [SCRIPT] var stacktrace = getstacktrace() assert_notequal([], stacktrace) @@ -122,11 +134,9 @@ func Test_zzz_stacktrace_vim9() assert_true(has_key(d, 'lnum')) endfor endtry + call assert_equal([], v:stacktrace) [SCRIPT] call CheckDefSuccess(lines) - " FIXME: v:stacktrace is not cleared after the exception handling, and this - " test has to be run as the last one because of this. - " call assert_equal([], v:stacktrace) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim index eb88ea6f5f..6ff233fff0 100644 --- a/test/old/testdir/test_termdebug.vim +++ b/test/old/testdir/test_termdebug.vim @@ -391,7 +391,8 @@ endfunc function Test_termdebug_save_restore_variables() " saved mousemodel - let &mousemodel='' + "let &mousemodel='' + let &mousemodel='extend' " saved keys nnoremap K :echo "hello world!"<cr> @@ -414,7 +415,8 @@ function Test_termdebug_save_restore_variables() quit! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_true(empty(&mousemodel)) + "call assert_true(empty(&mousemodel)) + call assert_equal(&mousemodel, 'extend') call assert_true(empty(expected_map_minus)) call assert_equal(expected_map_K.rhs, maparg('K', 'n', 0, 1).rhs) diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 39388cbc27..328ac502bf 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -470,7 +470,7 @@ func Test_Visual_Block() \ "\t{", \ "\t}"], getline(1, '$')) - close! + bw! endfunc " Test for 'p'ut in visual block mode @@ -1082,7 +1082,7 @@ func Test_star_register() delmarks < > call assert_fails('*yank', 'E20:') - close! + bw! endfunc " Test for changing text in visual mode with 'exclusive' selection @@ -1098,7 +1098,7 @@ func Test_exclusive_selection() call assert_equal('l one', getline(1)) set virtualedit& set selection& - close! + bw! endfunc " Test for starting linewise visual with a count. @@ -1155,7 +1155,7 @@ func Test_visual_inner_block() 8,9d call cursor(5, 1) call assert_beeps('normal ViBiB') - close! + bw! endfunc func Test_visual_put_in_block() @@ -2764,4 +2764,22 @@ func Test_visual_block_exclusive_selection_adjusted() set selection&vim endfunc +" the following caused a Heap-Overflow, because Vim was accessing outside of a +" line end +func Test_visual_pos_buffer_heap_overflow() + set virtualedit=all + args Xa Xb + all + call setline(1, ['', '', '']) + call cursor(3, 1) + wincmd w + call setline(1, 'foobar') + normal! $lv0 + all + call setreg('"', 'baz') + normal! [P + set virtualedit= + bw! Xa Xb +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/testutil.lua b/test/testutil.lua index 007e017ac6..3226bfeb1e 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -22,13 +22,6 @@ local M = { paths = Paths, } ---- @param p string ---- @return string -local function relpath(p) - p = vim.fs.normalize(p) - return (p:gsub('^' .. uv.cwd, '')) -end - --- @param path string --- @return boolean function M.isdir(path) @@ -45,14 +38,15 @@ end --- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all --- string values in a table (recursively). --- ---- @param obj string|table ---- @return any +--- @generic T: string|table +--- @param obj T +--- @return T|nil function M.fix_slashes(obj) if not M.is_os('win') then return obj end if type(obj) == 'string' then - local ret = obj:gsub('\\', '/') + local ret = string.gsub(obj, '\\', '/') return ret elseif type(obj) == 'table' then --- @cast obj table<any,any> @@ -482,7 +476,8 @@ function M.check_cores(app, force) -- luacheck: ignore -- "./Xtest-tmpdir/" => "Xtest%-tmpdir" local local_tmpdir = nil if tmpdir_is_local and tmpdir then - local_tmpdir = vim.pesc(relpath(tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) + local_tmpdir = + vim.pesc(vim.fs.relpath(assert(vim.uv.cwd()), tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) end local db_cmd --- @type string diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua index bdc111de2c..2c52aa9217 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -58,11 +58,11 @@ describe('mbyte', function() lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) local str = ffi.string(buf) if 1 > 2 then -- for debugging - local tabel = {} + local tbl = {} for i = 1, #str do - table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + table.insert(tbl, string.format('0x%02x', string.byte(str, i))) end - print('{ ' .. table.concat(tabel, ', ') .. ' }') + print('{ ' .. table.concat(tbl, ', ') .. ' }') io.stdout:flush() end return { str, firstc[0] } diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua index b9c9ceaa85..1f5b42485f 100644 --- a/test/unit/optionstr_spec.lua +++ b/test/unit/optionstr_spec.lua @@ -11,8 +11,8 @@ local check_ff_value = function(ff) end describe('check_ff_value', function() - itp('views empty string as valid', function() - eq(1, check_ff_value('')) + itp('views empty string as invalid', function() + eq(0, check_ff_value('')) end) itp('views "unix", "dos" and "mac" as valid', function() diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index db0aa3c575..6ff3c18d2a 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -28,6 +28,7 @@ local bit = require('bit') --- @field parser_sos function --- @field parser_text function --- @field print_color function +--- @field schar_get fun(any, any):integer --- @field screen_sb_clear function --- @field screen_sb_popline function --- @field screen_sb_pushline function @@ -43,6 +44,8 @@ local bit = require('bit') --- @field state_setpenattr function --- @field state_settermprop function --- @field term_output function +--- @field utf_ptr2char fun(any):integer +--- @field utf_ptr2len fun(any):integer --- @field vterm_input_write function --- @field vterm_keyboard_end_paste function --- @field vterm_keyboard_key function @@ -360,7 +363,7 @@ local function screen_cell(row, col, expected, screen) pos['row'] = row pos['col'] = col - local cell = t.ffi.new('VTermScreenCell') + local cell = t.ffi.new('VTermScreenCell') ---@type any vterm.vterm_screen_get_cell(screen, pos, cell) local buf = t.ffi.new('unsigned char[32]') @@ -1131,7 +1134,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) push('\x1b[0F', vt) cursor(0, 0, state) - -- Cursor Horizonal Absolute + -- Cursor Horizontal Absolute push('\n', vt) cursor(1, 0, state) push('\x1b[20G', vt) @@ -1705,12 +1708,6 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) push('#', vt) expect('putglyph 23 1 0,0') - -- Designate G0=UK - reset(state, nil) - push('\x1b(A', vt) - push('#', vt) - expect('putglyph a3 1 0,0') - -- Designate G0=DEC drawing reset(state, nil) push('\x1b(0', vt) @@ -3104,7 +3101,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) screen ) - -- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" + -- Outputting CJK doublewidth in 80th column should wraparound to next line and not crash" reset(nil, screen) push('\x1b[80G\xEF\xBC\x90', vt) screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) |