diff options
67 files changed, 1288 insertions, 769 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fe735cad2..ef83c22155 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -149,8 +149,10 @@ View the [Clang report] to see potential bugs found by the Clang ### Coverity -[Coverity](https://scan.coverity.com/projects/neovim-neovim) runs against the -master build. To view the defects, just request access; you will be approved. +Coverity runs against the master build. To view the defects you must +[request access](https://scan.coverity.com/projects/neovim-neovim) (Coverity +does not have a "public" view), then you will be approved as soon as +a maintainer sees the email. - Use this format for commit messages (where `{id}` is the CID (Coverity ID); ([example](https://github.com/neovim/neovim/pull/804))): @@ -283,7 +285,7 @@ If you need to modify or debug the documentation flow, these are the main files: ### Lua docstrings Use [LuaLS] annotations in Lua docstrings to annotate parameter types, return -types, etc. See [:help dev-doc-lua][dev-doc-lua]. +types, etc. See [:help dev-lua-doc][dev-lua-doc]. - The template for function documentation is: ```lua @@ -331,8 +333,8 @@ as context, use the `-W` argument as well. [complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow [conventional_commits]: https://www.conventionalcommits.org [dev-doc-guide]: https://neovim.io/doc/user/develop.html#dev-doc -[dev-doc-lua]: https://neovim.io/doc/user/develop.html#dev-lua-doc -[LuaLS]: https://github.com/LuaLS/lua-language-server/wiki/Annotations +[dev-lua-doc]: https://neovim.io/doc/user/develop.html#dev-lua-doc +[LuaLS]: https://luals.github.io/wiki/annotations/ [gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html [gh]: https://cli.github.com/ [git-bisect]: http://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git diff --git a/INSTALL.md b/INSTALL.md index 3b882ab6c9..2246058bca 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -155,17 +155,6 @@ Python (`:python`) support is installable via the package manager on Debian unst sudo apt-get install python3-neovim -If installing via appimage, the following commands may be helpful in updating default paths: - - # CUSTOM_NVIM_PATH=/usr/local/bin/nvim.appimage - # Set the above with the correct path, then run the rest of the commands: - set -u - sudo update-alternatives --install /usr/bin/ex ex "${CUSTOM_NVIM_PATH}" 110 - sudo update-alternatives --install /usr/bin/vi vi "${CUSTOM_NVIM_PATH}" 110 - sudo update-alternatives --install /usr/bin/view view "${CUSTOM_NVIM_PATH}" 110 - sudo update-alternatives --install /usr/bin/vim vim "${CUSTOM_NVIM_PATH}" 110 - sudo update-alternatives --install /usr/bin/vimdiff vimdiff "${CUSTOM_NVIM_PATH}" 110 - ### Exherbo Linux Exhereses for scm and released versions are currently available in repository `::medvid`. Python client (with GTK+ GUI included) and Qt5 GUI are also available as suggestions: @@ -336,17 +325,6 @@ If you're using an older version Ubuntu you must use: For instructions to install the Python modules, see [`:help provider-python`]. -If you want to use Neovim for some (or all) of the editor alternatives, use the following commands: - - sudo update-alternatives --install /usr/bin/vi vi /usr/bin/nvim 60 - sudo update-alternatives --config vi - sudo update-alternatives --install /usr/bin/vim vim /usr/bin/nvim 60 - sudo update-alternatives --config vim - sudo update-alternatives --install /usr/bin/editor editor /usr/bin/nvim 60 - sudo update-alternatives --config editor - -Note, however, that special interfaces, like `view` for `nvim -R`, are not supported. (See [#1646](https://github.com/neovim/neovim/issues/1646) and [#2008](https://github.com/neovim/neovim/pull/2008).) - ### Void-Linux Neovim can be installed using the xbps package manager diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 90b752d250..afc4da8def 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -37,6 +37,7 @@ option(USE_BUNDLED "Use bundled dependencies." ON) option(USE_BUNDLED_LIBUV "Use the bundled libuv." ${USE_BUNDLED}) option(USE_BUNDLED_LIBVTERM "Use the bundled libvterm." ${USE_BUNDLED}) option(USE_BUNDLED_LPEG "Use the bundled lpeg." ${USE_BUNDLED}) +# PUC Lua is only used for tests, unless explicitly requested. option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF) option(USE_BUNDLED_LUAJIT "Use the bundled version of luajit." ${USE_BUNDLED}) option(USE_BUNDLED_LUV "Use the bundled version of luv." ${USE_BUNDLED}) diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index 811a9501fe..4decfbced7 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -7802,7 +7802,7 @@ fun! s:NetrwMarkFileMove(islocal) let movecmd = netrw#WinPath(movecmd).movecmdargs " call Decho("windows exception: movecmd<".movecmd."> (#1: had a space)",'~'.expand("<slnum>")) else - let movecmd = netrw#WinPath(movecmd) + let movecmd = netrw#WinPath(g:netrw_localmovecmd) " call Decho("windows exception: movecmd<".movecmd."> (#2: no space)",'~'.expand("<slnum>")) endif else @@ -7816,10 +7816,6 @@ fun! s:NetrwMarkFileMove(islocal) endif if !g:netrw_cygwin && (has("win32") || has("win95") || has("win64") || has("win16")) let fname= substitute(fname,'/','\\','g') - if g:netrw_keepdir - " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1 - let fname= b:netrw_curdir."\\".fname - endif endif " call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand("<slnum>")) let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index ce3af01073..e43574fe86 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -784,9 +784,6 @@ OptionSet After setting an option (except during are not global-local it is the old local value. - OptionSet is not triggered on startup and for - the 'key' option for obvious reasons. - Usage example: Check for the existence of the directory in the 'backupdir' and 'undodir' options, create the directory if it doesn't @@ -800,6 +797,8 @@ OptionSet After setting an option (except during Non-recursive: |:set| in the autocommand does not trigger OptionSet again. + Not triggered on startup. + *QuickFixCmdPre* QuickFixCmdPre Before a quickfix command is run (|:make|, |:lmake|, |:grep|, |:lgrep|, |:grepadd|, diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index e3fc616df4..ad1b838578 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -911,9 +911,8 @@ start({config}, {opts}) *vim.lsp.start()* • `name` arbitrary name for the LSP client. Should be unique per language server. - • `cmd` command (in list form) used to start the language server. Must be - absolute, or found on `$PATH`. Shell constructs like `~` are not - expanded. + • `cmd` command string[] or function, described at + |vim.lsp.start_client()|. • `root_dir` path to the project root. By default this is used to decide if an existing client should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the root by traversing @@ -953,15 +952,16 @@ start_client({config}) *vim.lsp.start_client()* Parameters: ~ • {config} (`lsp.ClientConfig`) Configuration for the server: - • cmd: (string[]|fun(dispatchers: table):table) command a - list of strings treated like |jobstart()|. The command - must launch the language server process. `cmd` can also be - a function that creates an RPC client. The function - receives a dispatchers table and must return a table with - the functions `request`, `notify`, `is_closing` and - `terminate` See |vim.lsp.rpc.request()| and - |vim.lsp.rpc.notify()| For TCP there is a built-in rpc - client factory: |vim.lsp.rpc.connect()| + • cmd: (string[]|fun(dispatchers: table):table) command + string[] that launches the language server (treated as in + |jobstart()|, must be absolute or on `$PATH`, shell + constructs like "~" are not expanded), or function that + creates an RPC client. Function receives a `dispatchers` + table and returns a table with member functions `request`, + `notify`, `is_closing` and `terminate`. See + |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|. For TCP + there is a builtin RPC client factory: + |vim.lsp.rpc.connect()| • cmd_cwd: (string, default=|getcwd()|) Directory to launch the `cmd` process. Not related to `root_dir`. • cmd_env: (table) Environment flags to pass to the LSP on @@ -1479,7 +1479,11 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* Lua module: vim.lsp.inlay_hint *lsp-inlay_hint* enable({bufnr}, {enable}) *vim.lsp.inlay_hint.enable()* - Enable/disable/toggle inlay hints for a buffer + Enables or disables inlay hints for a buffer. + + To "toggle", pass the inverse of `is_enabled()`: >lua + vim.lsp.inlay_hint.enable(0, not vim.lsp.inlay_hint.is_enabled()) +< Note: ~ This API is pre-release (unstable). diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 924eb99e4e..24474255ba 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -3753,9 +3753,9 @@ filter({f}, {src}, {...}) *vim.iter.filter()* < Parameters: ~ - • {f} (`fun(...):bool`) Filter function. Accepts the current iterator - or table values as arguments and returns true if those values - should be kept in the final table + • {f} (`fun(...):boolean`) Filter function. Accepts the current + iterator or table values as arguments and returns true if those + values should be kept in the final table • {src} (`table|function`) Table or iterator function to filter Return: ~ @@ -3768,7 +3768,7 @@ Iter:all({pred}) *Iter:all()* Returns true if all items in the iterator match the given predicate. Parameters: ~ - • {pred} (`fun(...):bool`) Predicate function. Takes all values + • {pred} (`fun(...):boolean`) Predicate function. Takes all values returned from the previous stage in the pipeline as arguments and returns true if the predicate matches. @@ -3777,7 +3777,7 @@ Iter:any({pred}) *Iter:any()* predicate. Parameters: ~ - • {pred} (`fun(...):bool`) Predicate function. Takes all values + • {pred} (`fun(...):boolean`) Predicate function. Takes all values returned from the previous stage in the pipeline as arguments and returns true if the predicate matches. @@ -3826,7 +3826,7 @@ Iter:filter({f}) *Iter:filter()* < Parameters: ~ - • {f} (`fun(...):bool`) Takes all values returned from the previous + • {f} (`fun(...):boolean`) Takes all values returned from the previous stage in the pipeline and returns false or nil if the current iterator element should be removed. diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 03fe5c7b81..e80969c583 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -1048,7 +1048,7 @@ CTRL-I Go to [count] newer cursor position in jump list |tui-modifyOtherKeys| or |tui-csiu|, CTRL-I can be mapped separately from <Tab>, on the condition that both keys are mapped, otherwise the mapping applies to - both. + both. Except in tmux: https://github.com/tmux/tmux/issues/2705 *:ju* *:jumps* :ju[mps] Print the jump list (not a motion command). diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 9405d73fd0..59ac3b7f34 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2485,7 +2485,7 @@ A jump table for the options with a short description can be found at |Q_op|. Characters to fill the statuslines, vertical separators and special lines in the window. It is a comma-separated list of items. Each item has a name, a colon - and the value of that item: + and the value of that item: |E1511| item default Used for ~ stl ' ' statusline of the current window @@ -2523,7 +2523,7 @@ A jump table for the options with a short description can be found at |Q_op|. < For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items single-byte and multibyte characters are supported. But double-width - characters are not supported. + characters are not supported. |E1512| The highlighting used for these items: item highlight group ~ @@ -3823,7 +3823,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'listchars' 'lcs' string (default "tab:> ,trail:-,nbsp:+") global or local to window |global-local| Strings to use in 'list' mode and for the |:list| command. It is a - comma-separated list of string settings. + comma-separated list of string settings. *E1511* *lcs-eol* eol:c Character to show at the end of each line. When @@ -3902,7 +3902,7 @@ A jump table for the options with a short description can be found at |Q_op|. omitted. The characters ':' and ',' should not be used. UTF-8 characters can - be used. All characters must be single width. + be used. All characters must be single width. *E1512* Each character can be specified as hex: >vim set listchars=eol:\\x24 diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index e6b5b06744..4312716b22 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -85,10 +85,10 @@ You can find this in the user manual, section |11.3|. *W325* The default |SwapExists| handler (|default-autocmds|) skips the |E325| prompt -(selects "(E)dit") if the swapfile owner process (1) is still running and (2) -was started by the current user. This presumes that you normally don't want -to be bothered with the |ATTENTION| message just because you happen to edit -the same file from multiple Nvim instances. In the worst case (a system +(and automatically chooses "(E)dit") if the swapfile owner process is still +running and owned by the current user. This presumes that you normally don't +want to be bothered with the |ATTENTION| message just because you happen to +edit the same file from multiple Nvim instances. In the worst case (a system crash) there will be more than one swapfile for the file; use |:recover| to inspect all of its swapfiles. diff --git a/runtime/ftplugin/gdscript.vim b/runtime/ftplugin/gdscript.vim new file mode 100644 index 0000000000..692afdd0ea --- /dev/null +++ b/runtime/ftplugin/gdscript.vim @@ -0,0 +1,68 @@ +" Vim filetype plugin file +" Language: gdscript (Godot game engine scripting language) +" Maintainer: Maxim Kim <habamax@gmail.com> +" Website: https://github.com/habamax/vim-gdscript +" +" This file has been manually translated from Vim9 script. + +if exists("b:did_ftplugin") | finish | endif + +let s:save_cpo = &cpo +set cpo&vim + +let b:did_ftplugin = 1 +let b:undo_ftplugin = 'setlocal cinkeys<' + \ .. '| setlocal indentkeys<' + \ .. '| setlocal commentstring<' + \ .. '| setlocal suffixesadd<' + \ .. '| setlocal foldexpr<' + \ .. '| setlocal foldignore<' + +setlocal cinkeys-=0# +setlocal indentkeys-=0# +setlocal suffixesadd=.gd +setlocal commentstring=#\ %s +setlocal foldignore= +setlocal foldexpr=s:GDScriptFoldLevel() + + +function s:GDScriptFoldLevel() abort + let line = getline(v:lnum) + if line =~? '^\s*$' + return "-1" + endif + + let sw = shiftwidth() + let indent = indent(v:lnum) / sw + let indent_next = indent(nextnonblank(v:lnum + 1)) / sw + + if indent_next > indent && line =~# ':\s*$' + return $">{indent_next}" + else + return $"{indent}" + endif +endfunction + + +if !exists("g:no_plugin_maps") + " Next/Previous section + function s:NextSection(back, cnt) abort + for n in range(a:cnt) + call search('^\s*func\s', a:back ? 'bW' : 'W') + endfor + endfunction + + " Nvim: <scriptcmd> hasn't been ported yet. + " nnoremap <silent><buffer> ]] <scriptcmd>NextSection(false, v:count1)<CR> + " nnoremap <silent><buffer> [[ <scriptcmd>NextSection(true, v:count1)<CR> + nnoremap <silent><buffer> ]] <Cmd>call <SID>NextSection(v:false, v:count1)<CR> + nnoremap <silent><buffer> [[ <Cmd>call <SID>NextSection(v:true, v:count1)<CR> + xmap <buffer><expr> ]] $'<C-\><C-N>{v:count1}]]m>gv' + xmap <buffer><expr> [[ $'<C-\><C-N>{v:count1}[[m>gv' + let b:undo_ftplugin ..= + \ " | silent exe 'unmap <buffer> [['" + \ .. " | silent exe 'unmap <buffer> ]]'" +endif + +let &cpo = s:save_cpo +unlet s:save_cpo diff --git a/runtime/ftplugin/gdshader.vim b/runtime/ftplugin/gdshader.vim new file mode 100644 index 0000000000..f0d34ee5a6 --- /dev/null +++ b/runtime/ftplugin/gdshader.vim @@ -0,0 +1,13 @@ +" Vim filetype plugin file +" Language: Godot shading language +" Maintainer: Maxim Kim <habamax@gmail.com> +" Website: https://github.com/habamax/vim-gdscript +" +" This file has been manually translated from Vim9 script. + +if exists("b:did_ftplugin") | finish | endif +let b:did_ftplugin = 1 + +let b:undo_ftplugin = 'setlocal suffixesadd<' + +setlocal suffixesadd=.gdshader diff --git a/runtime/indent/gdscript.vim b/runtime/indent/gdscript.vim new file mode 100644 index 0000000000..bbb4f284f1 --- /dev/null +++ b/runtime/indent/gdscript.vim @@ -0,0 +1,154 @@ +" Vim indent file +" Language: gdscript (Godot game engine) +" Maintainer: Maxim Kim <habamax@gmail.com> +" Based on python indent file. +" +" This file has been manually translated from Vim9 script. + +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +let s:save_cpo = &cpo +set cpo&vim + +let s:undo_opts = "setl indentexpr< indentkeys< lisp< autoindent<" + +if exists('b:undo_indent') + let b:undo_indent ..= "|" .. s:undo_opts +else + let b:undo_indent = s:undo_opts +endif + +setlocal nolisp +setlocal autoindent +setlocal indentexpr=s:GDScriptIndent() +setlocal indentkeys+=<:>,=elif,=except + + +function s:GDScriptIndent() abort + " If this line is explicitly joined: If the previous line was also joined, + " line it up with that one, otherwise add two 'shiftwidth' + if getline(v:lnum - 1) =~# '\\$' + if v:lnum > 1 && getline(v:lnum - 2) =~# '\\$' + return indent(v:lnum - 1) + endif + return indent(v:lnum - 1) + (shiftwidth() * 2) + endif + + " If the start of the line is in a string don't change the indent. + if has('syntax_items') && synIDattr(synID(v:lnum, 1, 1), "name") =~# "String$" + return -1 + endif + + " Search backwards for the previous non-empty line. + let plnum = prevnonblank(v:lnum - 1) + + if plnum == 0 + " This is the first non-empty line, use zero indent. + return 0 + endif + + let plindent = indent(plnum) + let plnumstart = plnum + + " Get the line and remove a trailing comment. + " Use syntax highlighting attributes when possible. + let pline = getline(plnum) + let pline_len = strlen(pline) + if has('syntax_items') + " If the last character in the line is a comment, do a binary search for + " the start of the comment. synID() is slow, a linear search would take + " too long on a long line. + if synIDattr(synID(plnum, pline_len, 1), "name") =~# "\\(Comment\\|Todo\\)$" + let min = 1 + let max = pline_len + while min < max + let col = (min + max) / 2 + if synIDattr(synID(plnum, col, 1), "name") =~# "\\(Comment\\|Todo\\)$" + let max = col + else + let min = col + 1 + endif + endwhile + let pline = strpart(pline, 0, min - 1) + endif + else + let col = 0 + while col < pline_len + if pline[col] ==# '#' + let pline = strpart(pline, 0, col) + break + endif + let col = col + 1 + endwhile + endif + + + " When "inside" parenthesis: If at the first line below the parenthesis add + " one 'shiftwidth' ("inside" is simplified and not really checked) + " my_var = ( + " a + " + b + " + c + " ) + if pline =~# '[({\[]\s*$' + return indent(plnum) + shiftwidth() + endif + + + " If the previous line ended with a colon, indent this line + if pline =~# ':\s*$' + return plindent + shiftwidth() + endif + + " If the previous line was a stop-execution statement... + if getline(plnum) =~# '^\s*\(break\|continue\|raise\|return\|pass\)\>' + " See if the user has already dedented + if indent(v:lnum) > indent(plnum) - shiftwidth() + " If not, recommend one dedent + return indent(plnum) - shiftwidth() + endif + " Otherwise, trust the user + return -1 + endif + + " If the current line begins with a keyword that lines up with "try" + if getline(v:lnum) =~# '^\s*\(except\|finally\)\>' + let lnum = v:lnum - 1 + while lnum >= 1 + if getline(lnum) =~# '^\s*\(try\|except\)\>' + let ind = indent(lnum) + if ind >= indent(v:lnum) + return -1 " indent is already less than this + endif + return ind " line up with previous try or except + endif + let lnum = lnum - 1 + endwhile + return -1 " no matching "try"! + endif + + + " If the current line begins with a header keyword, dedent + if getline(v:lnum) =~# '^\s*\(elif\|else\)\>' + + " Unless the previous line was a one-liner + if getline(plnumstart) =~# '^\s*\(for\|if\|try\)\>' + return plindent + endif + + " Or the user has already dedented + if indent(v:lnum) <= plindent - shiftwidth() + return -1 + endif + + return plindent - shiftwidth() + endif + + return -1 +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 4fe601dfd5..52a21587f4 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -190,12 +190,18 @@ function vim._os_proc_children(ppid) return children end +--- @class vim.inspect.Opts +--- @field depth? integer +--- @field newline? string +--- @field process? fun(item:any, path: string[]): any + --- Gets a human-readable representation of the given object. --- ---@see |vim.print()| ---@see https://github.com/kikito/inspect.lua ---@see https://github.com/mpeterv/vinspect ---@return string +---@overload fun(x: any, opts?: vim.inspect.Opts): string vim.inspect = vim.inspect do diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 8c550fccdb..c9e2e302dc 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2197,7 +2197,7 @@ vim.bo.ft = vim.bo.filetype --- Characters to fill the statuslines, vertical separators and special --- lines in the window. --- It is a comma-separated list of items. Each item has a name, a colon ---- and the value of that item: +--- and the value of that item: `E1511` --- --- item default Used for ~ --- stl ' ' statusline of the current window @@ -2238,7 +2238,7 @@ vim.bo.ft = vim.bo.filetype --- --- For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items --- single-byte and multibyte characters are supported. But double-width ---- characters are not supported. +--- characters are not supported. `E1512` --- --- The highlighting used for these items: --- item highlight group ~ @@ -3829,7 +3829,7 @@ vim.o.list = false vim.wo.list = vim.o.list --- Strings to use in 'list' mode and for the `:list` command. It is a ---- comma-separated list of string settings. +--- comma-separated list of string settings. *E1511* --- --- *lcs-eol* --- eol:c Character to show at the end of each line. When @@ -3921,7 +3921,7 @@ vim.wo.list = vim.o.list --- omitted. --- --- The characters ':' and ',' should not be used. UTF-8 characters can ---- be used. All characters must be single width. +--- be used. All characters must be single width. *E1512* --- --- Each character can be specified as hex: --- diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index c7a1e8ebd3..03a1fe9a2f 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -180,10 +180,8 @@ local all_namespaces = {} ---@return vim.diagnostic.Severity? local function to_severity(severity) if type(severity) == 'string' then - return assert( - M.severity[string.upper(severity)], - string.format('Invalid severity: %s', severity) - ) + assert(M.severity[string.upper(severity)], string.format('Invalid severity: %s', severity)) + return M.severity[string.upper(severity)] end return severity end diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index 49d6f555da..764200dd36 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -77,7 +77,8 @@ function M.to_lpeg(pattern) }) local lpeg_pattern = p:match(pattern) --[[@as vim.lpeg.Pattern?]] - return assert(lpeg_pattern, 'Invalid glob') + assert(lpeg_pattern, 'Invalid glob') + return lpeg_pattern end return M diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index a63d5ba565..d720745110 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -181,7 +181,7 @@ end --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) --- ``` --- ----@param f fun(...):bool Takes all values returned from the previous stage +---@param f fun(...):boolean Takes all values returned from the previous stage --- in the pipeline and returns false or nil if the --- current iterator element should be removed. ---@return Iter @@ -884,7 +884,7 @@ end --- Returns true if any of the items in the iterator match the given predicate. --- ----@param pred fun(...):bool Predicate function. Takes all values returned from the previous +---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous --- stage in the pipeline as arguments and returns true if the --- predicate matches. function Iter.any(self, pred) @@ -908,7 +908,7 @@ end --- Returns true if all items in the iterator match the given predicate. --- ----@param pred fun(...):bool Predicate function. Takes all values returned from the previous +---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous --- stage in the pipeline as arguments and returns true if the --- predicate matches. function Iter.all(self, pred) @@ -1106,7 +1106,7 @@ end --- ---@see |Iter:filter()| --- ----@param f fun(...):bool Filter function. Accepts the current iterator or table values as +---@param f fun(...):boolean Filter function. Accepts the current iterator or table values as --- arguments and returns true if those values should be kept in the --- final table ---@param src table|function Table or iterator function to filter diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a02a93d559..f3448209ba 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -488,8 +488,7 @@ end --- See |vim.lsp.start_client()| for all available options. The most important are: --- --- - `name` arbitrary name for the LSP client. Should be unique per language server. ---- - `cmd` command (in list form) used to start the language server. Must be absolute, or found on ---- `$PATH`. Shell constructs like `~` are not expanded. +--- - `cmd` command string[] or function, described at |vim.lsp.start_client()|. --- - `root_dir` path to the project root. By default this is used to decide if an existing client --- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the --- root by traversing the file system upwards starting from the current directory until either @@ -666,13 +665,13 @@ end --- Field `cmd` in {config} is required. --- ---@param config (lsp.ClientConfig) Configuration for the server: ---- - cmd: (string[]|fun(dispatchers: table):table) command a list of ---- strings treated like |jobstart()|. The command must launch the language server ---- process. `cmd` can also be a function that creates an RPC client. ---- The function receives a dispatchers table and must return a table with the ---- functions `request`, `notify`, `is_closing` and `terminate` ---- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()| ---- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect()| +--- - cmd: (string[]|fun(dispatchers: table):table) command string[] that launches the language +--- server (treated as in |jobstart()|, must be absolute or on `$PATH`, shell constructs like +--- "~" are not expanded), or function that creates an RPC client. Function receives +--- a `dispatchers` table and returns a table with member functions `request`, `notify`, +--- `is_closing` and `terminate`. +--- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|. +--- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()| --- --- - cmd_cwd: (string, default=|getcwd()|) Directory to launch --- the `cmd` process. Not related to `root_dir`. diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index ce1680549e..4816b873ba 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -368,7 +368,13 @@ function M.is_enabled(bufnr) return bufstates[bufnr] and bufstates[bufnr].enabled or false end ---- Enable/disable/toggle inlay hints for a buffer +--- Enables or disables inlay hints for a buffer. +--- +--- To "toggle", pass the inverse of `is_enabled()`: +--- +--- ```lua +--- vim.lsp.inlay_hint.enable(0, not vim.lsp.inlay_hint.is_enabled()) +--- ``` --- --- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current --- @param enable (boolean|nil) true/nil to enable, false to disable diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index a249a10f47..971c4449e8 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -757,6 +757,11 @@ end) ---@param alias string language or filetype name ---@return string? # resolved parser name local function resolve_lang(alias) + -- validate that `alias` is a legal language + if not (alias and alias:match('[%w_]+') == alias) then + return + end + if has_parser(alias) then return alias end diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim index d4512525f9..8131639a11 100644 --- a/runtime/syntax/i3config.vim +++ b/runtime/syntax/i3config.vim @@ -2,8 +2,8 @@ " Language: i3 config file " Original Author: Josef Litos (JosefLitos/i3config.vim) " Maintainer: Quentin Hibon (github user hiqua) -" Version: 1.0.0 -" Last Change: 2023-11-11 +" Version: 1.0.2 +" Last Change: 2023-12-28 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -137,8 +137,7 @@ syn match i3ConfigIpcKeyword /ipc-socket/ contained syn match i3ConfigParamLine /^ipc-socket .*$/ contains=i3ConfigIpcKeyword " 4.24 Focus follows mouse -syn keyword i3ConfigFocusFollowsMouseOpts always contained -syn match i3ConfigKeyword /^focus_follows_mouse \(yes\|no\|always\)$/ contains=i3ConfigBoolean,i3ConfigFocusFollowsMouseOpts +syn match i3ConfigKeyword /^focus_follows_mouse \(yes\|no\)$/ contains=i3ConfigBoolean " 4.25 Mouse warping syn keyword i3ConfigMouseWarpingOpts output container none contained @@ -298,7 +297,6 @@ hi def link i3ConfigWorkspaceDir i3ConfigOption hi def link i3ConfigDotOperator i3ConfigOperator hi def link i3ConfigClientOpts i3ConfigOption hi def link i3ConfigIpcKeyword i3ConfigKeyword -hi def link i3ConfigFocusFollowsMouseOpts i3ConfigOption hi def link i3ConfigMouseWarpingOpts i3ConfigOption hi def link i3ConfigPopupFullscreenOpts i3ConfigOption hi def link i3ConfigFocusWrappingOpts i3ConfigOption diff --git a/runtime/syntax/swayconfig.vim b/runtime/syntax/swayconfig.vim index 7b1c889d6d..69fe26d370 100644 --- a/runtime/syntax/swayconfig.vim +++ b/runtime/syntax/swayconfig.vim @@ -2,8 +2,8 @@ " Language: sway config file " Original Author: Josef Litos (JosefLitos/i3config.vim) " Maintainer: James Eapen <james.eapen@vai.org> -" Version: 1.0.0 -" Last Change: 2023-09-14 +" Version: 1.0.2 +" Last Change: 2023-12-28 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -43,6 +43,12 @@ syn region swayConfigExecBlock start=/exec\(_always\)\? {/ end=/^}$/ contains=i3 syn keyword swayConfigFloatingModifierOpts normal inverse contained syn match i3ConfigKeyword /^floating_modifier [$a-zA-Z0-9+]\+ \(normal\|inverse\)$/ contains=i3ConfigVariable,i3ConfigBindModkey,swayConfigFloatingModifierOpts +syn keyword swayConfigSmartGapsOpts toggle contained +syn match i3ConfigKeyword /^smart_gaps toggle$/ contains=i3ConfigSmartGapOpts,i3ConfigBoolean,swayConfigSmartGapsOpts + +syn keyword swayConfigFocusFollowsMouseOpts always contained +syn match i3ConfigKeyword /^focus_follows_mouse always$/ contains=i3ConfigBoolean,swayConfigFocusFollowsMouseOpts + syn match i3ConfigKeyword /^hide_edge_borders --i3 \w*$/ contains=i3ConfigEdgeKeyword,i3ConfigShParam syn keyword i3ConfigBarOpts swaybar_command gaps height pango_markup status_edge_padding status_padding wrap_scroll tray_bindcode tray_bindsym icon_theme contained @@ -118,7 +124,9 @@ syn region swayConfigOutput start=/^output/ skip=/\\$/ end=/$/ contains=swayCon syn region swayConfigOutput start=/^output .* {$/ end=/}$/ contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps,i3ConfigParen keepend extend " Define the highlighting. +hi def link swayConfigSmartGapsOpts i3ConfigOption hi def link swayConfigFloatingModifierOpts i3ConfigOption +hi def link swayConfigFocusFollowsMouseOpts i3ConfigOption hi def link swayConfigBindKeyword i3ConfigBindKeyword hi def link swayConfigXOpt i3ConfigOption hi def link swayConfigInhibitKeyword i3ConfigCommand diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 49417e72bb..5986a399d9 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1123,10 +1123,8 @@ function M._test() 'if "expected" is given, "actual" is also required' ) if expected then - return assert( - cond, - ('expected %s, got: %s'):format(vim.inspect(expected), vim.inspect(actual)) - ) + assert(cond, ('expected %s, got: %s'):format(vim.inspect(expected), vim.inspect(actual))) + return cond else return assert(cond) end diff --git a/src/nvim/README.md b/src/nvim/README.md index b264c918b6..9ef39cc90f 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -255,6 +255,12 @@ region is repainted internally. To also highlight excess internal redraws, use - http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +Data structures +--------------- + +Buffer text is stored as a tree of line segments, defined in [memline.c](https://github.com/neovim/neovim/blob/v0.9.5/src/nvim/memline.c#L8-L35). +The central idea is found in [ml_find_line](https://github.com/neovim/neovim/blob/v0.9.5/src/nvim/memline.c#L2800). + Nvim lifecycle -------------- diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index b44158c890..a385161beb 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1302,9 +1302,11 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) } aco->save_curwin_handle = curwin->handle; - aco->save_curbuf = curbuf; aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle; aco->save_State = State; + if (bt_prompt(curbuf)) { + aco->save_prompt_insert = curbuf->b_prompt_insert; + } if (win != NULL) { // There is a window for "buf" in the current tab page, make it the @@ -1417,6 +1419,9 @@ win_found: curbuf = curwin->w_buffer; // May need to restore insert mode for a prompt buffer. entering_window(curwin); + if (bt_prompt(curbuf)) { + curbuf->b_prompt_insert = aco->save_prompt_insert; + } prevwin = win_find_by_handle(aco->save_prevwin_handle); vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h index d1f1e16e1a..6535f8a7ea 100644 --- a/src/nvim/autocmd_defs.h +++ b/src/nvim/autocmd_defs.h @@ -14,7 +14,6 @@ /// Struct to save values in before executing autocommands for a buffer that is /// not the current buffer. typedef struct { - buf_T *save_curbuf; ///< saved curbuf int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 handle_T save_curwin_handle; ///< ID of saved curwin handle_T new_curwin_handle; ///< ID of new curwin @@ -23,6 +22,7 @@ typedef struct { char *globaldir; ///< saved value of globaldir bool save_VIsual_active; ///< saved VIsual_active int save_State; ///< saved State + int save_prompt_insert; ///< saved b_prompt_insert } aco_save_T; typedef struct { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index e8c41cb608..cead63b88d 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -61,10 +61,10 @@ /// structure with variables passed between win_line() and other functions typedef struct { - linenr_T lnum; ///< line number to be drawn - foldinfo_T foldinfo; ///< fold info for this line + const linenr_T lnum; ///< line number to be drawn + const foldinfo_T foldinfo; ///< fold info for this line - int startrow; ///< first row in the window to be drawn + const int startrow; ///< first row in the window to be drawn int row; ///< row in the window, excl w_winrow colnr_T vcol; ///< virtual column, before wrapping @@ -901,6 +901,26 @@ static void fix_for_boguscols(winlinevars_T *wlv) wlv->boguscols = 0; } +static int get_rightmost_vcol(win_T *wp, const int *color_cols) +{ + int ret = 0; + + if (wp->w_p_cuc) { + ret = wp->w_virtcol; + } + + if (color_cols) { + // determine rightmost colorcolumn to possibly draw + for (int i = 0; color_cols[i] >= 0; i++) { + if (ret < color_cols[i]) { + ret = color_cols[i]; + } + } + } + + return ret; +} + /// Display line "lnum" of window "wp" on the screen. /// wp->w_virtcol needs to be valid. /// @@ -918,11 +938,7 @@ static void fix_for_boguscols(winlinevars_T *wlv) int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, spellvars_T *spv, foldinfo_T foldinfo) { - winlinevars_T wlv; // variables passed between functions - colnr_T vcol_prev = -1; // "wlv.vcol" of previous character - char *line; // current line - char *ptr; // current position in "line" ScreenGrid *grid = &wp->w_grid; // grid specific to the window static char *at_end_str = ""; // used for p_extra when displaying curwin->w_p_lcs_chars.eol @@ -936,8 +952,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s int fromcol_prev = -2; // start of inverting after cursor bool noinvcur = false; // don't invert the cursor bool lnum_in_visual_area = false; - pos_T pos; - ptrdiff_t v; bool attr_pri = false; // char_attr has priority bool area_highlighting = false; // Visual or incsearch highlighting in this line @@ -948,7 +962,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s int decor_attr = 0; // attributes desired by syntax and extmarks bool has_syntax = false; // this buffer has syntax highl. int folded_attr = 0; // attributes for folded line - int save_did_emsg; int eol_hl_off = 0; // 1 if highlighted char after EOL #define SPWORDLEN 150 char nextline[SPWORDLEN * 2]; // text with start of the next line @@ -967,8 +980,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s int change_end = -1; // last col of changed area bool in_multispace = false; // in multiple consecutive spaces int multispace_pos = 0; // position in lcs-multispace string - int line_attr_save; - int line_attr_lowprio_save; bool search_attr_from_match = false; // if search_attr is from :match bool has_decor = false; // this buffer has decoration @@ -1005,16 +1016,17 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s assert(startrow < endrow); - CLEAR_FIELD(wlv); - - wlv.lnum = lnum; - wlv.foldinfo = foldinfo; - wlv.startrow = startrow; - wlv.row = startrow; - wlv.fromcol = -10; - wlv.tocol = MAXCOL; - wlv.vcol_sbr = -1; - wlv.old_boguscols = 0; + // variables passed between functions + winlinevars_T wlv = { + .lnum = lnum, + .foldinfo = foldinfo, + .startrow = startrow, + .row = startrow, + .fromcol = -10, + .tocol = MAXCOL, + .vcol_sbr = -1, + .old_boguscols = 0, + }; buf_T *buf = wp->w_buffer; const bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); @@ -1027,7 +1039,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && !has_fold && !end_fill) { // Prepare for syntax highlighting in this line. When there is an // error, stop syntax highlighting. - save_did_emsg = did_emsg; + int save_did_emsg = did_emsg; did_emsg = false; syntax_start(wp, lnum); if (did_emsg) { @@ -1095,7 +1107,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } else if (bot->col == MAXCOL) { wlv.tocol = MAXCOL; } else { - pos = *bot; + pos_T pos = *bot; if (*p_sel == 'e') { getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, NULL, NULL); } else { @@ -1130,8 +1142,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s wlv.fromcol = 0; } if (lnum == curwin->w_cursor.lnum + search_match_lines) { - pos.lnum = lnum; - pos.col = search_match_endcol; + pos_T pos = { + .lnum = lnum, + .col = search_match_endcol, + }; getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL); } // do at least one character; happens when past end of line @@ -1224,10 +1238,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s area_highlighting = true; } - if (cul_screenline) { - line_attr_save = wlv.line_attr; - line_attr_lowprio_save = wlv.line_attr_lowprio; - } + int line_attr_save = wlv.line_attr; + int line_attr_lowprio_save = wlv.line_attr_lowprio; if (spv->spv_has_spell && col_rows == 0) { // Prepare for spell checking. @@ -1252,14 +1264,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // Trick: skip a few chars for C/shell/Vim comments nextline[SPWORDLEN] = NUL; if (lnum < wp->w_buffer->b_ml.ml_line_count) { - line = ml_get_buf(wp->w_buffer, lnum + 1); + char *line = ml_get_buf(wp->w_buffer, lnum + 1); spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); } assert(!end_fill); - line = ml_get_buf(wp->w_buffer, lnum); + char *line = ml_get_buf(wp->w_buffer, lnum); // If current line is empty, check first word in next line for capital. - ptr = skipwhite(line); + char *ptr = skipwhite(line); if (*ptr == NUL) { spv->spv_cap_col = 0; spv->spv_capcol_lnum = lnum + 1; @@ -1274,25 +1286,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s nextlinecol = MAXCOL; nextline_idx = 0; } else { - v = (ptrdiff_t)strlen(line); - if (v < SPWORDLEN) { + const size_t line_len = strlen(line); + if (line_len < SPWORDLEN) { // Short line, use it completely and append the start of the // next line. nextlinecol = 0; - memmove(nextline, line, (size_t)v); - STRMOVE(nextline + v, nextline + SPWORDLEN); - nextline_idx = (int)v + 1; + memmove(nextline, line, line_len); + STRMOVE(nextline + line_len, nextline + SPWORDLEN); + nextline_idx = (int)line_len + 1; } else { // Long line, use only the last SPWORDLEN bytes. - nextlinecol = (int)v - SPWORDLEN; + nextlinecol = (int)line_len - SPWORDLEN; memmove(nextline, line + nextlinecol, SPWORDLEN); nextline_idx = SPWORDLEN + 1; } } } - line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum); - ptr = line; + // current line + char *line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum); + // current position in "line" + char *ptr = line; colnr_T trailcol = MAXCOL; // start of trailing spaces colnr_T leadcol = 0; // start of leading spaces @@ -1316,20 +1330,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the // first character to be displayed. - if (wp->w_p_wrap) { - v = startrow == 0 ? wp->w_skipcol : 0; - } else { - v = wp->w_leftcol; - } - if (v > 0 && col_rows == 0) { + const int start_col = wp->w_p_wrap + ? (startrow == 0 ? wp->w_skipcol : 0) + : wp->w_leftcol; + + if (start_col > 0 && col_rows == 0) { char *prev_ptr = ptr; chartabsize_T cts; int charsize = 0; int head = 0; init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr); - cts.cts_max_head_vcol = (int)v; - while (cts.cts_vcol < v && *cts.cts_ptr != NUL) { + cts.cts_max_head_vcol = start_col; + while (cts.cts_vcol < start_col && *cts.cts_ptr != NUL) { head = 0; charsize = win_lbr_chartabsize(&cts, &head); cts.cts_vcol += charsize; @@ -1365,22 +1378,22 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // - 'virtualedit' is set, or // - the visual mode is active, // the end of the line may be before the start of the displayed part. - if (wlv.vcol < v && (wp->w_p_cuc - || wlv.color_cols - || virtual_active() - || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { - wlv.vcol = (colnr_T)v; + if (wlv.vcol < start_col && (wp->w_p_cuc + || wlv.color_cols + || virtual_active() + || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { + wlv.vcol = start_col; } // Handle a character that's not completely on the screen: Put ptr at // that character but skip the first few screen characters. - if (wlv.vcol > v) { + if (wlv.vcol > start_col) { wlv.vcol -= charsize; ptr = prev_ptr; } - if (v > wlv.vcol) { - wlv.skip_cells = (int)v - wlv.vcol - head; + if (start_col > wlv.vcol) { + wlv.skip_cells = start_col - wlv.vcol - head; } // Adjust for when the inverted text is before the screen, @@ -1398,14 +1411,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // When spell checking a word we need to figure out the start of the // word and if it's badly spelled or not. if (spv->spv_has_spell) { - size_t len; colnr_T linecol = (colnr_T)(ptr - line); hlf_T spell_hlf = HLF_COUNT; - pos = wp->w_cursor; + pos_T pos = wp->w_cursor; wp->w_cursor.lnum = lnum; wp->w_cursor.col = linecol; - len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); + size_t len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); // spell_move_to() may call ml_get() and make "line" invalid line = ml_get_buf(wp->w_buffer, lnum); @@ -1455,8 +1467,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } if (col_rows == 0 && !has_fold && !end_fill) { - v = ptr - line; - area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + const int v = (int)(ptr - line); + area_highlighting |= prepare_search_hl_line(wp, lnum, v, &line, &screen_search_hl, &search_attr, &search_attr_from_match); ptr = line + v; // "line" may have been updated @@ -1513,7 +1525,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (sign_num_attr == 0) { statuscol.num_attr = get_line_number_attr(wp, &wlv); } - v = ptr - line; + const int v = (int)(ptr - line); draw_statuscol(wp, &wlv, lnum, wlv.row - startrow - wlv.filler_lines, &statuscol); if (wp->w_redr_statuscol) { break; @@ -1670,8 +1682,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // Check for start/end of 'hlsearch' and other matches. // After end, check for start/end of next match. // When another match, have to check for start again. - v = ptr - line; - search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &screen_search_hl, + const int v = (int)(ptr - line); + search_attr = update_search_hl(wp, lnum, v, &line, &screen_search_hl, &has_match_conc, &match_conc, lcs_eol_todo, &on_last_col, &search_attr_from_match); ptr = line + v; // "line" may have been changed @@ -1734,7 +1746,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } if (draw_folded && wlv.n_extra == 0 && wlv.col == win_col_offset) { - v = ptr - line; + const int v = (int)(ptr - line); linenr_T lnume = lnum + foldinfo.fi_lines - 1; memset(buf_fold, ' ', FOLD_TEXT_LEN); wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt); @@ -1839,7 +1851,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // skip writing the buffer line itself mb_schar = NUL; } else { - char *prev_ptr = ptr; + const char *prev_ptr = ptr; // first byte of next char int c0 = (uint8_t)(*ptr); @@ -1917,20 +1929,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s decor_attr = 0; if (extra_check) { - bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0; + const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0; bool can_spell = !no_plain_buffer; // Get extmark and syntax attributes, unless still at the start of the line // (double-wide char that doesn't fit). - v = ptr - line; + const int v = (int)(ptr - line); + const ptrdiff_t prev_v = prev_ptr - line; if (has_syntax && v > 0) { // Get the syntax attribute for the character. If there // is an error, disable syntax highlighting. - save_did_emsg = did_emsg; + int save_did_emsg = did_emsg; did_emsg = false; - decor_attr = get_syntax_attr((colnr_T)v - 1, - spv->spv_has_spell ? &can_spell : NULL, false); + decor_attr = get_syntax_attr(v - 1, spv->spv_has_spell ? &can_spell : NULL, false); if (did_emsg) { wp->w_s->b_syn_error = true; @@ -1947,6 +1959,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // have made it invalid. line = ml_get_buf(wp->w_buffer, lnum); ptr = line + v; + prev_ptr = line + prev_v; // no concealing past the end of the line, it interferes // with line highlighting. @@ -1980,28 +1993,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // Only do this when there is no syntax highlighting, the // @Spell cluster is not used or the current syntax item // contains the @Spell cluster. - v = ptr - line; - if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) { + int v1 = (int)(ptr - line); + if (spv->spv_has_spell && v1 >= word_end && v1 > cur_checked_col) { spell_attr = 0; // do not calculate cap_col at the end of the line or when // only white space is following if (mb_schar != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) { char *p; hlf_T spell_hlf = HLF_COUNT; - v -= mb_l - 1; + v1 -= mb_l - 1; // Use nextline[] if possible, it has the start of the // next line concatenated. if ((prev_ptr - line) - nextlinecol >= 0) { p = nextline + ((prev_ptr - line) - nextlinecol); } else { - p = prev_ptr; + p = (char *)prev_ptr; } spv->spv_cap_col -= (int)(prev_ptr - line); size_t tmplen = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, spv->spv_unchanged); assert(tmplen <= INT_MAX); int len = (int)tmplen; - word_end = (int)v + len; + word_end = v1 + len; // In Insert mode only highlight a word that // doesn't touch the cursor. @@ -2448,8 +2461,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (mb_schar == NUL && eol_hl_off == 0) { // flag to indicate whether prevcol equals startcol of search_hl or // one of the matches - bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, - (colnr_T)(ptr - line) - 1); + const bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, + (colnr_T)(ptr - line) - 1); // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last @@ -2485,10 +2498,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s &wlv.char_attr); } - int eol_attr = wlv.char_attr; - if (wlv.cul_attr) { - eol_attr = hl_combine_attr(wlv.cul_attr, eol_attr); - } + const int eol_attr = wlv.cul_attr + ? hl_combine_attr(wlv.cul_attr, wlv.char_attr) + : wlv.char_attr; + linebuf_attr[wlv.off] = eol_attr; linebuf_vcol[wlv.off] = MAXCOL; wlv.col++; @@ -2501,15 +2514,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // At end of the text line. if (mb_schar == NUL) { // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. - if (wp->w_p_wrap) { - v = wlv.startrow == 0 ? wp->w_skipcol : 0; - } else { - v = wp->w_leftcol; - } // check if line ends before left margin - if (wlv.vcol < v + wlv.col - win_col_off(wp)) { - wlv.vcol = (colnr_T)v + wlv.col - win_col_off(wp); + if (wlv.vcol < start_col + wlv.col - win_col_off(wp)) { + wlv.vcol = start_col + wlv.col - win_col_off(wp); } // Get rid of the boguscols now, we want to draw until the right // edge for 'cursorcolumn'. @@ -2521,7 +2529,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s bool has_virttext = false; // Make sure alignment is the same regardless // if listchars=eol:X is used or not. - int eol_skip = (lcs_eol_todo && eol_hl_off == 0 ? 1 : 0); + const int eol_skip = (lcs_eol_todo && eol_hl_off == 0 ? 1 : 0); if (has_decor) { has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); @@ -2529,37 +2537,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (((wp->w_p_cuc && wp->w_virtcol >= vcol_hlc(wlv) - eol_hl_off - && wp->w_virtcol < grid->cols * (ptrdiff_t)(wlv.row - startrow + 1) + v + && wp->w_virtcol < grid->cols * (ptrdiff_t)(wlv.row - startrow + 1) + start_col && lnum != wp->w_cursor.lnum) || wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr || wlv.diff_hlf != 0 || has_virttext)) { - int rightmost_vcol = 0; - - if (wp->w_p_cuc) { - rightmost_vcol = wp->w_virtcol; - } + int rightmost_vcol = get_rightmost_vcol(wp, wlv.color_cols); + const int cuc_attr = win_hl_attr(wp, HLF_CUC); + const int mc_attr = win_hl_attr(wp, HLF_MC); - if (wlv.color_cols) { - // determine rightmost colorcolumn to possibly draw - for (int i = 0; wlv.color_cols[i] >= 0; i++) { - if (rightmost_vcol < wlv.color_cols[i]) { - rightmost_vcol = wlv.color_cols[i]; - } - } - } - - int cuc_attr = win_hl_attr(wp, HLF_CUC); - int mc_attr = win_hl_attr(wp, HLF_MC); - - int diff_attr = 0; if (wlv.diff_hlf == HLF_TXD) { wlv.diff_hlf = HLF_CHD; } - if (wlv.diff_hlf != 0) { - diff_attr = win_hl_attr(wp, (int)wlv.diff_hlf); - } - int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr); + const int diff_attr = wlv.diff_hlf != 0 + ? win_hl_attr(wp, (int)wlv.diff_hlf) + : 0; + + const int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr); if (base_attr || wlv.line_attr || has_virttext) { rightmost_vcol = INT_MAX; } @@ -2808,13 +2802,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && wlv.p_extra != at_end_str) || (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) || has_more_inline_virt(&wlv, ptr - line))) { - bool wrap = wp->w_p_wrap // Wrapping enabled. - && wlv.filler_todo <= 0 // Not drawing diff filler lines. - && lcs_eol_todo // Haven't printed the lcs_eol character. - && wlv.row != endrow - 1 // Not the last line being displayed. - && (grid->cols == Columns // Window spans the width of the screen, - || ui_has(kUIMultigrid)) // or has dedicated grid. - && !wp->w_p_rl; // Not right-to-left. + const bool wrap = wp->w_p_wrap // Wrapping enabled. + && wlv.filler_todo <= 0 // Not drawing diff filler lines. + && lcs_eol_todo // Haven't printed the lcs_eol character. + && wlv.row != endrow - 1 // Not the last line being displayed. + && (grid->cols == Columns // Window spans the width of the screen, + || ui_has(kUIMultigrid)) // or has dedicated grid. + && !wp->w_p_rl; // Not right-to-left. int draw_col = wlv.col - wlv.boguscols; if (virt_line_offset >= 0) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d10214b48f..2c5c291216 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2277,6 +2277,7 @@ static buf_T *cmdpreview_open_buf(void) /// /// @return Pointer to command preview window if succeeded, NULL if failed. static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf) + FUNC_ATTR_NONNULL_ALL { win_T *save_curwin = curwin; @@ -2548,10 +2549,10 @@ static bool cmdpreview_may_show(CommandLineState *s) cmdpreview_prepare(&cpinfo); // Open preview buffer if inccommand=split. - if (!icm_split) { - cmdpreview_bufnr = 0; - } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) { - abort(); + if (icm_split && (cmdpreview_buf = cmdpreview_open_buf()) == NULL) { + // Failed to create preview buffer, so disable preview. + set_string_option_direct(kOptInccommand, "nosplit", 0, SID_NONE); + icm_split = false; } // Setup preview namespace if it's not already set. if (!cmdpreview_ns) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 68ddb95e38..0185b62daa 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1703,10 +1703,10 @@ static void didset_options2(void) highlight_changed(); // Parse default for 'fillchars'. - set_chars_option(curwin, curwin->w_p_fcs, kFillchars, true); + set_chars_option(curwin, curwin->w_p_fcs, kFillchars, true, NULL, 0); // Parse default for 'listchars'. - set_chars_option(curwin, curwin->w_p_lcs, kListchars, true); + set_chars_option(curwin, curwin->w_p_lcs, kListchars, true, NULL, 0); // Parse default for 'wildmode'. check_opt_wim(); @@ -4998,8 +4998,8 @@ void didset_window_options(win_T *wp, bool valid_cursor) check_colorcolumn(wp); briopt_check(wp); fill_culopt_flags(NULL, wp); - set_chars_option(wp, wp->w_p_fcs, kFillchars, true); - set_chars_option(wp, wp->w_p_lcs, kListchars, true); + set_chars_option(wp, wp->w_p_fcs, kFillchars, true, NULL, 0); + set_chars_option(wp, wp->w_p_lcs, kListchars, true, NULL, 0); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl check_blending(wp); set_winbar_win(wp, false, valid_cursor); diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 65f6bc76ba..db33c94be3 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2818,7 +2818,7 @@ return { Characters to fill the statuslines, vertical separators and special lines in the window. It is a comma-separated list of items. Each item has a name, a colon - and the value of that item: + and the value of that item: |E1511| item default Used for ~ stl ' ' statusline of the current window @@ -2856,7 +2856,7 @@ return { < For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items single-byte and multibyte characters are supported. But double-width - characters are not supported. + characters are not supported. |E1512| The highlighting used for these items: item highlight group ~ @@ -4879,7 +4879,7 @@ return { deny_duplicates = true, desc = [=[ Strings to use in 'list' mode and for the |:list| command. It is a - comma-separated list of string settings. + comma-separated list of string settings. *E1511* *lcs-eol* eol:c Character to show at the end of each line. When @@ -4958,7 +4958,7 @@ return { omitted. The characters ':' and ',' should not be used. UTF-8 characters can - be used. All characters must be single width. + be used. All characters must be single width. *E1512* Each character can be specified as hex: >vim set listchars=eol:\\x24 diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index cef9443e63..e37569ee56 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -62,6 +62,10 @@ static const char e_backupext_and_patchmode_are_equal[] = N_("E589: 'backupext' and 'patchmode' are equal"); static const char e_showbreak_contains_unprintable_or_wide_character[] = N_("E595: 'showbreak' contains unprintable or wide character"); +static const char e_wrong_number_of_characters_for_field_str[] + = N_("E1511: Wrong number of characters for field \"%s\""); +static const char e_wrong_character_width_for_field_str[] + = N_("E1512: Wrong character width for field \"%s\""); static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -879,14 +883,16 @@ int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches) /// The global 'listchars' or 'fillchars' option is changed. static const char *did_set_global_chars_option(win_T *win, char *val, CharsOption what, - int opt_flags) + int opt_flags, char *errbuf, size_t errbuflen) { const char *errmsg = NULL; char **local_ptr = (what == kListchars) ? &win->w_p_lcs : &win->w_p_fcs; // only apply the global value to "win" when it does not have a // local value - errmsg = set_chars_option(win, val, what, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); + errmsg = set_chars_option(win, val, what, + **local_ptr == NUL || !(opt_flags & OPT_GLOBAL), + errbuf, errbuflen); if (errmsg != NULL) { return errmsg; } @@ -904,7 +910,7 @@ static const char *did_set_global_chars_option(win_T *win, char *val, CharsOptio // here, so ignore the return value. char *opt = (what == kListchars) ? wp->w_p_lcs : wp->w_p_fcs; if (*opt == NUL) { - set_chars_option(wp, opt, what, true); + set_chars_option(wp, opt, what, true, errbuf, errbuflen); } } @@ -921,13 +927,17 @@ const char *did_set_chars_option(optset_T *args) const char *errmsg = NULL; if (varp == &p_lcs) { // global 'listchars' - errmsg = did_set_global_chars_option(win, *varp, kListchars, args->os_flags); + errmsg = did_set_global_chars_option(win, *varp, kListchars, args->os_flags, + args->os_errbuf, args->os_errbuflen); } else if (varp == &p_fcs) { // global 'fillchars' - errmsg = did_set_global_chars_option(win, *varp, kFillchars, args->os_flags); + errmsg = did_set_global_chars_option(win, *varp, kFillchars, args->os_flags, + args->os_errbuf, args->os_errbuflen); } else if (varp == &win->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(win, *varp, kListchars, true); + errmsg = set_chars_option(win, *varp, kListchars, true, + args->os_errbuf, args->os_errbuflen); } else if (varp == &win->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(win, *varp, kFillchars, true); + errmsg = set_chars_option(win, *varp, kFillchars, true, + args->os_errbuf, args->os_errbuflen); } return errmsg; @@ -2668,14 +2678,27 @@ static const struct chars_tab lcs_tab[] = { { NULL, "leadmultispace", NULL, NULL }, }; +static char *field_value_err(char *errbuf, size_t errbuflen, const char *fmt, const char *field) +{ + if (errbuf == NULL) { + return ""; + } + vim_snprintf(errbuf, errbuflen, _(fmt), field); + return errbuf; +} + /// Handle setting 'listchars' or 'fillchars'. /// Assume monocell characters /// -/// @param value points to either the global or the window-local value. -/// @param what kListchars or kFillchars -/// @param apply if false, do not store the flags, only check for errors. +/// @param value points to either the global or the window-local value. +/// @param what kListchars or kFillchars +/// @param apply if false, do not store the flags, only check for errors. +/// @param errbuf buffer for error message, can be NULL if it won't be used. +/// @param errbuflen size of error buffer. +/// /// @return error message, NULL if it's OK. -const char *set_chars_option(win_T *wp, const char *value, CharsOption what, bool apply) +const char *set_chars_option(win_T *wp, const char *value, CharsOption what, bool apply, + char *errbuf, size_t errbuflen) { const char *last_multispace = NULL; // Last occurrence of "multispace:" const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" @@ -2736,9 +2759,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo int i; for (i = 0; i < entries; i++) { const size_t len = strlen(tab[i].name); - if (!(strncmp(p, tab[i].name, len) == 0 - && p[len] == ':' - && p[len + 1] != NUL)) { + if (!(strncmp(p, tab[i].name, len) == 0 && p[len] == ':')) { continue; } @@ -2751,13 +2772,17 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo while (*s != NUL && *s != ',') { schar_T c1 = get_encoded_char_adv(&s); if (c1 == 0) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_character_width_for_field_str, + tab[i].name); } multispace_len++; } if (multispace_len == 0) { // lcs-multispace cannot be an empty string - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_number_of_characters_for_field_str, + tab[i].name); } p = s; } else { @@ -2782,13 +2807,17 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo while (*s != NUL && *s != ',') { schar_T c1 = get_encoded_char_adv(&s); if (c1 == 0) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_character_width_for_field_str, + tab[i].name); } lead_multispace_len++; } if (lead_multispace_len == 0) { // lcs-leadmultispace cannot be an empty string - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_number_of_characters_for_field_str, + tab[i].name); } p = s; } else { @@ -2805,24 +2834,37 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo } const char *s = p + len + 1; + if (*s == NUL) { + return field_value_err(errbuf, errbuflen, + e_wrong_number_of_characters_for_field_str, + tab[i].name); + } schar_T c1 = get_encoded_char_adv(&s); if (c1 == 0) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_character_width_for_field_str, + tab[i].name); } schar_T c2 = 0; schar_T c3 = 0; if (tab[i].cp == &lcs_chars.tab2) { if (*s == NUL) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_number_of_characters_for_field_str, + tab[i].name); } c2 = get_encoded_char_adv(&s); if (c2 == 0) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_character_width_for_field_str, + tab[i].name); } if (!(*s == ',' || *s == NUL)) { c3 = get_encoded_char_adv(&s); if (c3 == 0) { - return e_invarg; + return field_value_err(errbuf, errbuflen, + e_wrong_character_width_for_field_str, + tab[i].name); } } } @@ -2839,6 +2881,10 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo } p = s; break; + } else { + return field_value_err(errbuf, errbuflen, + e_wrong_number_of_characters_for_field_str, + tab[i].name); } } @@ -2893,17 +2939,17 @@ char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) /// @return an untranslated error message if any of them is invalid, NULL otherwise. const char *check_chars_options(void) { - if (set_chars_option(curwin, p_lcs, kListchars, false) != NULL) { + if (set_chars_option(curwin, p_lcs, kListchars, false, NULL, 0) != NULL) { return e_conflicts_with_value_of_listchars; } - if (set_chars_option(curwin, p_fcs, kFillchars, false) != NULL) { + if (set_chars_option(curwin, p_fcs, kFillchars, false, NULL, 0) != NULL) { return e_conflicts_with_value_of_fillchars; } FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, wp->w_p_lcs, kListchars, true) != NULL) { + if (set_chars_option(wp, wp->w_p_lcs, kListchars, true, NULL, 0) != NULL) { return e_conflicts_with_value_of_listchars; } - if (set_chars_option(wp, wp->w_p_fcs, kFillchars, true) != NULL) { + if (set_chars_option(wp, wp->w_p_fcs, kFillchars, true, NULL, 0) != NULL) { return e_conflicts_with_value_of_fillchars; } } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9cd0c64bfe..6b8b73a2a0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1312,6 +1312,9 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); } +/// Flushes TUI grid state to a buffer (which is later flushed to the TTY by `flush_buf`). +/// +/// @see flush_buf void tui_flush(TUIData *tui) { UGrid *grid = &tui->grid; @@ -2327,6 +2330,9 @@ static size_t flush_buf_end(TUIData *tui, char *buf, size_t len) return offset; } +/// Flushes the rendered buffer to the TTY. +/// +/// @see tui_flush static void flush_buf(TUIData *tui) { uv_write_t req; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index d343fb5fa0..d517f07b32 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2389,7 +2389,9 @@ static void u_undoredo(bool undo, bool do_buf_event) // When text has been changed, possibly the start of the next line // may have SpellCap that should be removed or it needs to be // displayed. Schedule the next line for redrawing just in case. - if (spell_check_window(curwin) && bot <= curbuf->b_ml.ml_line_count) { + // Also just in case the line had a sign which needs to be removed. + if ((spell_check_window(curwin) || curbuf->b_signs_with_text) + && bot <= curbuf->b_ml.ml_line_count) { redrawWinline(curwin, bot); } diff --git a/test/client/msgpack_rpc_stream.lua b/test/client/msgpack_rpc_stream.lua index 4b4f30e0ec..7131940a58 100644 --- a/test/client/msgpack_rpc_stream.lua +++ b/test/client/msgpack_rpc_stream.lua @@ -22,6 +22,9 @@ function Response:send(value, is_error) self._msgpack_rpc_stream._stream:write(data) end +--- @class test.MsgpackRpcStream +--- @field private _stream test.Stream +--- @field private __pack table local MsgpackRpcStream = {} MsgpackRpcStream.__index = MsgpackRpcStream diff --git a/test/client/session.lua b/test/client/session.lua index 86b4ee7103..cf3d8c4f25 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -1,6 +1,12 @@ local uv = vim.uv local MsgpackRpcStream = require('test.client.msgpack_rpc_stream') +--- @class test.Session +--- @field private _pending_messages string[] +--- @field private _msgpack_rpc_stream test.MsgpackRpcStream +--- @field private _prepare uv.uv_prepare_t +--- @field private _timer uv.uv_timer_t +--- @field private _is_running boolean local Session = {} Session.__index = Session if package.loaded['jit'] then @@ -26,7 +32,7 @@ end local function coroutine_exec(func, ...) local args = { ... } - local on_complete + local on_complete --- @type function? if #args > 0 and type(args[#args]) == 'function' then -- completion callback @@ -54,6 +60,8 @@ function Session.new(stream) }, Session) end +--- @param timeout integer? +--- @return string? function Session:next_message(timeout) local function on_request(method, args, response) table.insert(self._pending_messages, { 'request', method, args, response }) @@ -86,6 +94,10 @@ function Session:notify(method, ...) self._msgpack_rpc_stream:write(method, { ... }) end +--- @param method string +--- @param ... any +--- @return boolean +--- @return table function Session:request(method, ...) local args = { ... } local err, result diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index 9e9a69e0a1..0540c44eb2 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -1,18 +1,28 @@ local uv = vim.uv +--- @class test.Stream +--- @field write fun(self, data: string|string[]) +--- @field read_start fun(self, cb: fun(chunk: string)) +--- @field read_stop fun(self) +--- @field close fun(self, signal?: string) + +--- @class vim.StdioStream : test.Stream +--- @field private _in uv.uv_pipe_t +--- @field private _out uv.uv_pipe_t local StdioStream = {} StdioStream.__index = StdioStream function StdioStream.open() local self = setmetatable({ - _in = uv.new_pipe(false), - _out = uv.new_pipe(false), + _in = assert(uv.new_pipe(false)), + _out = assert(uv.new_pipe(false)), }, StdioStream) self._in:open(0) self._out:open(1) return self end +--- @param data string|string[] function StdioStream:write(data) self._out:write(data) end @@ -35,11 +45,14 @@ function StdioStream:close() self._out:close() end +--- @class test.SocketStream : test.Stream +--- @field package _stream_error? string +--- @field package _socket uv.uv_pipe_t local SocketStream = {} SocketStream.__index = SocketStream function SocketStream.open(file) - local socket = uv.new_pipe(false) + local socket = assert(uv.new_pipe(false)) local self = setmetatable({ _socket = socket, _stream_error = nil, @@ -51,7 +64,7 @@ function SocketStream.open(file) end function SocketStream.connect(host, port) - local socket = uv.new_tcp() + local socket = assert(uv.new_tcp()) local self = setmetatable({ _socket = socket, _stream_error = nil, @@ -96,9 +109,20 @@ function SocketStream:close() uv.close(self._socket) end +--- @class test.ChildProcessStream : test.Stream +--- @field private _proc uv.uv_process_t +--- @field private _pid integer +--- @field private _child_stdin uv.uv_pipe_t +--- @field private _child_stdout uv.uv_pipe_t +--- @field status integer +--- @field signal integer local ChildProcessStream = {} ChildProcessStream.__index = ChildProcessStream +--- @param argv string[] +--- @param env string[]? +--- @param io_extra uv.uv_pipe_t? +--- @return test.ChildProcessStream function ChildProcessStream.spawn(argv, env, io_extra) local self = setmetatable({ _child_stdin = uv.new_pipe(false), @@ -106,13 +130,15 @@ function ChildProcessStream.spawn(argv, env, io_extra) _exiting = false, }, ChildProcessStream) local prog = argv[1] - local args = {} + local args = {} --- @type string[] for i = 2, #argv do args[#args + 1] = argv[i] end + --- @diagnostic disable-next-line:missing-fields self._proc, self._pid = uv.spawn(prog, { stdio = { self._child_stdin, self._child_stdout, 2, io_extra }, args = args, + --- @diagnostic disable-next-line:assign-type-mismatch env = env, }, function(status, signal) self.status = status diff --git a/test/deprecated.lua b/test/deprecated.lua deleted file mode 100644 index e30dfcf3ab..0000000000 --- a/test/deprecated.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Island of Misfit Toys - -local M = {} - -function M.redir_exec() - error('redir_exec is deprecated, use nvim_exec2() or pcall_err()') -end - -return M diff --git a/test/format_string.lua b/test/format_string.lua new file mode 100644 index 0000000000..777fb652e8 --- /dev/null +++ b/test/format_string.lua @@ -0,0 +1,168 @@ +local luaassert = require('luassert') + +local M = {} + +local SUBTBL = { + '\\000', + '\\001', + '\\002', + '\\003', + '\\004', + '\\005', + '\\006', + '\\007', + '\\008', + '\\t', + '\\n', + '\\011', + '\\012', + '\\r', + '\\014', + '\\015', + '\\016', + '\\017', + '\\018', + '\\019', + '\\020', + '\\021', + '\\022', + '\\023', + '\\024', + '\\025', + '\\026', + '\\027', + '\\028', + '\\029', + '\\030', + '\\031', +} + +--- @param v any +--- @return string +local function format_float(v) + -- On windows exponent appears to have three digits and not two + local ret = ('%.6e'):format(v) + local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') + return l .. '.' .. f .. 'e' .. es .. e +end + +-- Formats Lua value `v`. +-- +-- TODO(justinmk): redundant with vim.inspect() ? +-- +-- "Nice table formatting similar to screen:snapshot_util()". +-- Commit: 520c0b91a528 +function M.format_luav(v, indent, opts) + opts = opts or {} + local linesep = '\n' + local next_indent_arg = nil + local indent_shift = opts.indent_shift or ' ' + local next_indent + local nl = '\n' + if indent == nil then + indent = '' + linesep = '' + next_indent = '' + nl = ' ' + else + next_indent_arg = indent .. indent_shift + next_indent = indent .. indent_shift + end + local ret = '' + if type(v) == 'string' then + if opts.literal_strings then + ret = v + else + local quote = opts.dquote_strings and '"' or "'" + ret = quote + .. tostring(v) + :gsub(opts.dquote_strings and '["\\]' or "['\\]", '\\%0') + :gsub('[%z\1-\31]', function(match) + return SUBTBL[match:byte() + 1] + end) + .. quote + end + elseif type(v) == 'table' then + if v == vim.NIL then + ret = 'REMOVE_THIS' + else + local processed_keys = {} + ret = '{' .. linesep + local non_empty = false + local format_luav = M.format_luav + for i, subv in ipairs(v) do + ret = ('%s%s%s,%s'):format(ret, next_indent, format_luav(subv, next_indent_arg, opts), nl) + processed_keys[i] = true + non_empty = true + end + for k, subv in pairs(v) do + if not processed_keys[k] then + if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then + ret = ret .. next_indent .. k .. ' = ' + else + ret = ('%s%s[%s] = '):format(ret, next_indent, format_luav(k, nil, opts)) + end + ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl + non_empty = true + end + end + if nl == ' ' and non_empty then + ret = ret:sub(1, -3) + end + ret = ret .. indent .. '}' + end + elseif type(v) == 'number' then + if v % 1 == 0 then + ret = ('%d'):format(v) + else + ret = format_float(v) + end + elseif type(v) == 'nil' then + ret = 'nil' + elseif type(v) == 'boolean' then + ret = (v and 'true' or 'false') + else + print(type(v)) + -- Not implemented yet + luaassert(false) + end + return ret +end + +-- Like Python repr(), "{!r}".format(s) +-- +-- Commit: 520c0b91a528 +function M.format_string(fmt, ...) + local i = 0 + local args = { ... } + local function getarg() + i = i + 1 + return args[i] + end + local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) + local subfmt = match:gsub('%*', function() + return tostring(getarg()) + end) + local arg = nil + if subfmt:sub(-1) ~= '%' then + arg = getarg() + end + if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then + -- %r is like built-in %q, but it is supposed to single-quote strings and + -- not double-quote them, and also work not only for strings. + -- Builtin %q is replaced here as it gives invalid and inconsistent with + -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`, + -- lua leaves as-is. + arg = M.format_luav(arg, nil, { dquote_strings = (subfmt:sub(-1) == 'q') }) + subfmt = subfmt:sub(1, -2) .. 's' + end + if subfmt == '%e' then + return format_float(arg) + else + return subfmt:format(arg) + end + end) + return ret +end + +return M diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 8d9789a3ce..91f61b5053 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -8,7 +8,7 @@ local assert_alive = helpers.assert_alive local NIL = vim.NIL local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq local command = helpers.command -local command_output = helpers.api.command_output +local command_output = helpers.api.nvim_command_output local exec = helpers.exec local exec_capture = helpers.exec_capture local eval = helpers.eval @@ -19,6 +19,7 @@ local matches = helpers.matches local pesc = vim.pesc local mkdir_p = helpers.mkdir_p local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed +local async_meths = helpers.async_meths local is_os = helpers.is_os local parse_context = helpers.parse_context local request = helpers.request @@ -33,7 +34,7 @@ local insert = helpers.insert local skip = helpers.skip local pcall_err = helpers.pcall_err -local format_string = helpers.format_string +local format_string = require('test.format_string').format_string local intchar2lua = helpers.intchar2lua local mergedicts_copy = helpers.mergedicts_copy local endswith = vim.endswith @@ -76,7 +77,7 @@ describe('API', function() eq({ 'notification', 'nvim_error_event', - { error_types.Exception.id, 'Invalid method: nvim_bogus' }, + { error_types.Exception.id, 'Invalid method: bogus' }, }, next_msg()) -- error didn't close channel. assert_alive() @@ -84,7 +85,7 @@ describe('API', function() it('failed async request emits nvim_error_event', function() local error_types = api.nvim_get_api_info()[2].error_types - nvim_async('command', 'bogus') + async_meths.nvim_command('bogus') eq({ 'notification', 'nvim_error_event', @@ -2081,13 +2082,13 @@ describe('API', function() { ['rc'] = { 'hjkl' }, ['n'] = 97 }, }, - ['jumps'] = eval(([[ + ['jumps'] = eval((([[ filter(map(add( getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), 'filter( { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') - ]]):gsub('\n', '')), + ]]):gsub('\n', ''))), ['bufs'] = eval([[ filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)') @@ -2301,7 +2302,7 @@ describe('API', function() end) it('can show one line', function() - nvim_async('err_write', 'has bork\n') + async_meths.nvim_err_write('has bork\n') screen:expect([[ ^ | {0:~ }|*6 @@ -2310,7 +2311,7 @@ describe('API', function() end) it('shows return prompt when more than &cmdheight lines', function() - nvim_async('err_write', 'something happened\nvery bad\n') + async_meths.nvim_err_write('something happened\nvery bad\n') screen:expect([[ | {0:~ }|*3 @@ -2322,7 +2323,7 @@ describe('API', function() end) it('shows return prompt after all lines are shown', function() - nvim_async('err_write', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') + async_meths.nvim_err_write('FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') screen:expect([[ | {0:~ }| @@ -2337,8 +2338,8 @@ describe('API', function() it('handles multiple calls', function() -- without linebreak text is joined to one line - nvim_async('err_write', 'very ') - nvim_async('err_write', 'fail\n') + async_meths.nvim_err_write('very ') + async_meths.nvim_err_write('fail\n') screen:expect([[ ^ | {0:~ }|*6 @@ -2347,7 +2348,7 @@ describe('API', function() helpers.poke_eventloop() -- shows up to &cmdheight lines - nvim_async('err_write', 'more fail\ntoo fail\n') + async_meths.nvim_err_write('more fail\ntoo fail\n') screen:expect([[ | {0:~ }|*3 @@ -2360,7 +2361,7 @@ describe('API', function() end) it('NUL bytes in message', function() - nvim_async('err_write', 'aaa\0bbb\0\0ccc\nddd\0\0\0eee\n') + async_meths.nvim_err_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n') screen:expect { grid = [[ | @@ -2389,7 +2390,7 @@ describe('API', function() end) it('shows only one return prompt after all lines are shown', function() - nvim_async('err_writeln', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK') + async_meths.nvim_err_writeln('FAILURE\nERROR\nEXCEPTION\nTRACEBACK') screen:expect([[ | {0:~ }| @@ -3394,8 +3395,7 @@ describe('API', function() end) it('can show highlighted line', function() - nvim_async( - 'echo', + async_meths.nvim_echo( { { 'msg_a' }, { 'msg_b', 'Statement' }, { 'msg_c', 'Special' } }, true, {} @@ -3410,7 +3410,7 @@ describe('API', function() end) it('can show highlighted multiline', function() - nvim_async('echo', { { 'msg_a\nmsg_a', 'Statement' }, { 'msg_b', 'Special' } }, true, {}) + async_meths.nvim_echo({ { 'msg_a\nmsg_a', 'Statement' }, { 'msg_b', 'Special' } }, true, {}) screen:expect { grid = [[ | @@ -3431,7 +3431,7 @@ describe('API', function() it('can disable saving message history', function() command('set cmdheight=2') -- suppress Press ENTER - nvim_async('echo', { { 'msg\nmsg' }, { 'msg' } }, false, {}) + async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) eq('', exec_capture('messages')) end) end) diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 4c2e54cc6b..a6fdb919c5 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -222,7 +222,7 @@ describe('swapfile detection', function() screen2:expect(expected_no_dialog) -- With API call and shortmess+=F - async_meths.command('edit %') + async_meths.nvim_command('edit %') screen2:expect { any = [[Found a swap file by the name ".*]] .. [[Xtest_swapdialog_dir[/\].*]] diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index e9f8f30aa0..eddf336b6f 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -18,7 +18,7 @@ local fail = global_helpers.fail local module = {} local runtime_set = 'set runtimepath^=./build/lib/nvim/' -module.nvim_prog = (os.getenv('NVIM_PRG') or global_helpers.test_build_dir .. '/bin/nvim') +module.nvim_prog = (os.getenv('NVIM_PRG') or global_helpers.paths.test_build_dir .. '/bin/nvim') -- Default settings for the test session. module.nvim_set = ( 'set shortmess+=IS background=light termguicolors noswapfile noautoindent startofline' @@ -55,7 +55,7 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = '.' end -local prepend_argv +local prepend_argv --- @type string[]? if os.getenv('VALGRIND') then local log_file = os.getenv('VALGRIND_LOG') or 'valgrind-%p.log' @@ -79,7 +79,7 @@ elseif os.getenv('GDB') then end if prepend_argv then - local new_nvim_argv = {} + local new_nvim_argv = {} --- @type string[] local len = #prepend_argv for i = 1, len do new_nvim_argv[i] = prepend_argv[i] @@ -91,10 +91,13 @@ if prepend_argv then module.prepend_argv = prepend_argv end -local session, loop_running, last_error, method_error +local session --- @type test.Session? +local loop_running --- @type boolean? +local last_error --- @type string? +local method_error --- @type string? if not is_os('win') then - local sigpipe_handler = uv.new_signal() + local sigpipe_handler = assert(uv.new_signal()) uv.signal_start(sigpipe_handler, 'sigpipe', function() print('warning: got SIGPIPE signal. Likely related to a crash in nvim') end) @@ -108,10 +111,15 @@ function module.set_session(s) session = s end +--- @param method string +--- @param ... any +--- @return any function module.request(method, ...) + assert(session) local status, rv = session:request(method, ...) if not status then if loop_running then + --- @type string last_error = rv[2] session:stop() else @@ -121,12 +129,18 @@ function module.request(method, ...) return rv end +--- @param method string +--- @param ... any +--- @return any function module.request_lua(method, ...) return module.exec_lua([[return vim.api[...](select(2, ...))]], method, ...) end +--- @param timeout? integer +--- @return string? function module.next_msg(timeout) - return session:next_message(timeout and timeout or 10000) + assert(session) + return session:next_message(timeout or 10000) end function module.expect_twostreams(msgs1, msgs2) @@ -164,6 +178,7 @@ function module.expect_msg_seq(...) error('invalid args') end local ignore = arg1['ignore'] and arg1['ignore'] or {} + --- @type string[] local seqs = arg1['seqs'] and arg1['seqs'] or { ... } if type(ignore) ~= 'table' then error("'ignore' arg must be a list of strings") @@ -213,6 +228,7 @@ function module.expect_msg_seq(...) local message = result if type(result) == 'table' then -- 'eq' returns several things + --- @type string message = result.message end final_error = cat_err(final_error, message) @@ -234,8 +250,16 @@ function module.set_method_error(err) method_error = err end +--- @param lsession test.Session +--- @param request_cb function +--- @param notification_cb function +--- @param setup_cb function +--- @param timeout integer +--- @return {[1]: integer, [2]: string} function module.run_session(lsession, request_cb, notification_cb, setup_cb, timeout) - local on_request, on_notification, on_setup + local on_request --- @type function? + local on_notification --- @type function? + local on_setup --- @type function? if request_cb then function on_request(method, args) @@ -273,11 +297,12 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim end function module.run(request_cb, notification_cb, setup_cb, timeout) + assert(session) return module.run_session(session, request_cb, notification_cb, setup_cb, timeout) end function module.stop() - session:stop() + assert(session):stop() end function module.nvim_prog_abs() @@ -301,6 +326,7 @@ function module.expect_exit(fn_or_timeout, ...) eof_err_msg, module.pcall_err(function(timeout, fn, ...) fn(...) + assert(session) while session:next_message(timeout) do end if session.eof_err then @@ -311,26 +337,18 @@ function module.expect_exit(fn_or_timeout, ...) end end --- Evaluates a Vimscript expression. --- Fails on Vimscript error, but does not update v:errmsg. -function module.eval(expr) - return module.request('nvim_eval', expr) -end - --- Executes a Vimscript function via RPC. --- Fails on Vimscript error, but does not update v:errmsg. -function module.call(name, ...) - return module.request('nvim_call_function', name, { ... }) -end - --- Executes a Vimscript function via Lua. --- Fails on Vimscript error, but does not update v:errmsg. +--- Executes a Vimscript function via Lua. +--- Fails on Vimscript error, but does not update v:errmsg. +--- @param name string +--- @param ... any +--- @return any function module.call_lua(name, ...) return module.exec_lua([[return vim.call(...)]], name, ...) end --- Sends user input to Nvim. --- Does not fail on Vimscript error, but v:errmsg will be updated. +--- Sends user input to Nvim. +--- Does not fail on Vimscript error, but v:errmsg will be updated. +--- @param input string local function nvim_feed(input) while #input > 0 do local written = module.request('nvim_input', input) @@ -342,22 +360,27 @@ local function nvim_feed(input) end end +--- @param ... string function module.feed(...) for _, v in ipairs({ ... }) do nvim_feed(dedent(v)) end end +--- @param ... string function module.rawfeed(...) for _, v in ipairs({ ... }) do nvim_feed(dedent(v)) end end +---@param ... string[]? +---@return string[] function module.merge_args(...) local i = 1 - local argv = {} + local argv = {} --- @type string[] for anum = 1, select('#', ...) do + --- @type string[]? local args = select(anum, ...) if args then for _, arg in ipairs(args) do @@ -369,26 +392,29 @@ function module.merge_args(...) return argv end --- Removes Nvim startup args from `args` matching items in `args_rm`. --- --- - Special case: "-u", "-i", "--cmd" are treated specially: their "values" are also removed. --- - Special case: "runtimepath" will remove only { '--cmd', 'set runtimepath^=…', } --- --- Example: --- args={'--headless', '-u', 'NONE'} --- args_rm={'--cmd', '-u'} --- Result: --- {'--headless'} --- --- All matching cases are removed. --- --- Example: --- args={'--cmd', 'foo', '-N', '--cmd', 'bar'} --- args_rm={'--cmd', '-u'} --- Result: --- {'-N'} +--- Removes Nvim startup args from `args` matching items in `args_rm`. +--- +--- - Special case: "-u", "-i", "--cmd" are treated specially: their "values" are also removed. +--- - Special case: "runtimepath" will remove only { '--cmd', 'set runtimepath^=…', } +--- +--- Example: +--- args={'--headless', '-u', 'NONE'} +--- args_rm={'--cmd', '-u'} +--- Result: +--- {'--headless'} +--- +--- All matching cases are removed. +--- +--- Example: +--- args={'--cmd', 'foo', '-N', '--cmd', 'bar'} +--- args_rm={'--cmd', '-u'} +--- Result: +--- {'-N'} +--- @param args string[] +--- @param args_rm string[] +--- @return string[] local function remove_args(args, args_rm) - local new_args = {} + local new_args = {} --- @type string[] local skip_following = { '-u', '-i', '-c', '--cmd', '-s', '--listen' } if not args_rm or #args_rm == 0 then return { unpack(args) } @@ -433,7 +459,12 @@ function module.check_close() session = nil end ---- @param io_extra used for stdin_fd, see :help ui-option +--- @param argv string[] +--- @param merge boolean? +--- @param env string[]? +--- @param keep boolean +--- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option +--- @return test.Session function module.spawn(argv, merge, env, keep, io_extra) if not keep then module.check_close() @@ -469,16 +500,27 @@ function module.clear(...) return module.get_session() end --- same params as clear, but does returns the session instead --- of replacing the default session +--- same params as clear, but does returns the session instead +--- of replacing the default session +--- @return test.Session function module.spawn_argv(keep, ...) local argv, env, io_extra = module.new_argv(...) return module.spawn(argv, nil, env, keep, io_extra) end --- Builds an argument list for use in clear(). --- ----@see clear() for parameters. +--- @class test.new_argv.Opts +--- @field args? string[] +--- @field args_rm? string[] +--- @field env? table<string,string> +--- @field io_extra? uv.uv_pipe_t + +--- Builds an argument list for use in clear(). +--- +--- @see clear() for parameters. +--- @param ... string +--- @return string[] +--- @return string[]? +--- @return uv.uv_pipe_t? function module.new_argv(...) local args = { unpack(module.nvim_argv) } table.insert(args, '--headless') @@ -487,16 +529,17 @@ function module.new_argv(...) table.insert(args, '--listen') table.insert(args, _G._nvim_test_id) end - local new_args - local io_extra - local env = nil + local new_args --- @type string[] + local io_extra --- @type uv.uv_pipe_t? + local env --- @type string[]? + --- @type test.new_argv.Opts|string local opts = select(1, ...) if type(opts) ~= 'table' then new_args = { ... } else args = remove_args(args, opts.args_rm) if opts.env then - local env_opt = {} + local env_opt = {} --- @type table<string,string> for k, v in pairs(opts.env) do assert(type(k) == 'string') assert(type(v) == 'string') @@ -535,6 +578,7 @@ function module.new_argv(...) return args, env, io_extra end +--- @param ... string function module.insert(...) nvim_feed('i') for _, v in ipairs({ ... }) do @@ -544,8 +588,9 @@ function module.insert(...) nvim_feed('<ESC>') end --- Executes an ex-command by user input. Because nvim_input() is used, Vimscript --- errors will not manifest as client (lua) errors. Use command() for that. +--- Executes an ex-command by user input. Because nvim_input() is used, Vimscript +--- errors will not manifest as client (lua) errors. Use command() for that. +--- @param ... string function module.feed_command(...) for _, v in ipairs({ ... }) do if v:sub(1, 1) ~= '/' then @@ -598,8 +643,10 @@ function module.set_shell_powershell(fake) end function module.create_callindex(func) - local table = {} - setmetatable(table, { + return setmetatable({}, { + --- @param tbl table<any,function> + --- @param arg1 string + --- @return function __index = function(tbl, arg1) local ret = function(...) return func(arg1, ...) @@ -608,36 +655,34 @@ function module.create_callindex(func) return ret end, }) - return table end -local function ui(method, ...) - return module.request('nvim_ui_' .. method, ...) +--- @param method string +--- @param ... any +function module.nvim_async(method, ...) + assert(session):notify(method, ...) end -function module.nvim_async(method, ...) - session:notify('nvim_' .. method, ...) +--- Executes a Vimscript function via RPC. +--- Fails on Vimscript error, but does not update v:errmsg. +--- @param name string +--- @param ... any +--- @return any +function module.call(name, ...) + return module.request('nvim_call_function', name, { ... }) end module.async_meths = module.create_callindex(module.nvim_async) -module.uimeths = module.create_callindex(ui) - -local function create_bridge(request, call) - local function nvim(method, ...) - if vim.startswith(method, 'nvim_') then - return request(method, ...) - end - return request('nvim_' .. method, ...) - end - return { - fn = module.create_callindex(call), - api = module.create_callindex(nvim), - } -end +module.rpc = { + fn = module.create_callindex(module.call), + api = module.create_callindex(module.request), +} -module.rpc = create_bridge(module.request, module.call) -module.lua = create_bridge(module.request_lua, module.call_lua) +module.lua = { + fn = module.create_callindex(module.call_lua), + api = module.create_callindex(module.request_lua), +} module.describe_lua_and_rpc = function(describe) return function(what, tests) @@ -657,6 +702,7 @@ module.api = vim.api module.fn = vim.fn for name, fns in pairs(module.rpc) do + --- @diagnostic disable-next-line:no-unknown module[name] = fns end @@ -664,10 +710,14 @@ end -- v:errmsg will not be updated. module.command = module.api.nvim_command +-- Evaluates a Vimscript expression. +-- Fails on Vimscript error, but does not update v:errmsg. +module.eval = module.api.nvim_eval + function module.poke_eventloop() -- Execute 'nvim_eval' (a deferred function) to -- force at least one main_loop iteration - session:request('nvim_eval', '1') + module.api.nvim_eval('1') end function module.buf_lines(bufnr) @@ -689,6 +739,10 @@ function module.expect_any(contents) return ok(nil ~= string.find(module.curbuf_contents(), contents, 1, true)) end +--- @param expected any[] +--- @param received any[] +--- @param kind string +--- @return any function module.expect_events(expected, received, kind) if not pcall(eq, expected, received) then local msg = 'unexpected ' .. kind .. ' received.\n\n' @@ -728,6 +782,7 @@ function module.assert_visible(bufnr, visible) end end +--- @param path string local function do_rmdir(path) local stat = uv.fs_stat(path) if stat == nil then @@ -795,14 +850,17 @@ function module.exc_exec(cmd) return ret end +--- @param cond boolean +--- @param reason string +--- @return boolean function module.skip(cond, reason) if cond then + --- @type fun(reason: string) local pending = getfenv(2).pending pending(reason or 'FIXME') return true - else - return false end + return false end -- Calls pending() and returns `true` if the system is too slow to @@ -825,6 +883,8 @@ function module.exec(code) module.api.nvim_exec2(code, {}) end +--- @param code string +--- @return string function module.exec_capture(code) return module.api.nvim_exec2(code, { output = true }).output end @@ -832,7 +892,7 @@ end --- @param code string --- @return any function module.exec_lua(code, ...) - return module.api.exec_lua(code, { ... }) + return module.api.nvim_exec_lua(code, { ... }) end function module.get_pathsep() @@ -871,19 +931,24 @@ function module.new_pipename() return pipename end +--- @param provider string +--- @return string|false? function module.missing_provider(provider) if provider == 'ruby' or provider == 'node' or provider == 'perl' then + --- @type string? local e = module.fn['provider#' .. provider .. '#Detect']()[2] return e ~= '' and e or false elseif provider == 'python' or provider == 'python3' then local py_major_version = (provider == 'python3' and 3 or 2) + --- @type string? local e = module.fn['provider#pythonx#Detect'](py_major_version)[2] return e ~= '' and e or false - else - assert(false, 'Unknown provider: ' .. provider) end + assert(false, 'Unknown provider: ' .. provider) end +--- @param obj string|table +--- @return any function module.alter_slashes(obj) if not is_os('win') then return obj @@ -892,14 +957,14 @@ function module.alter_slashes(obj) local ret = obj:gsub('/', '\\') return ret elseif type(obj) == 'table' then - local ret = {} + --- @cast obj table<any,any> + local ret = {} --- @type table<any,any> for k, v in pairs(obj) do ret[k] = module.alter_slashes(v) end return ret - else - assert(false, 'expected string or table of strings, got ' .. type(obj)) end + assert(false, 'expected string or table of strings, got ' .. type(obj)) end local load_factor = 1 @@ -909,18 +974,25 @@ if global_helpers.is_ci() then module.request('nvim_command', 'source test/old/testdir/load.vim') load_factor = module.request('nvim_eval', 'g:test_load_factor') end + +--- @param num number +--- @return number function module.load_adjust(num) return math.ceil(num * load_factor) end +--- @param ctx table<string,any> +--- @return table function module.parse_context(ctx) - local parsed = {} + local parsed = {} --- @type table<string,any> for _, item in ipairs({ 'regs', 'jumps', 'bufs', 'gvars' }) do + --- @param v any parsed[item] = vim.tbl_filter(function(v) return type(v) == 'table' end, module.call('msgpackparse', ctx[item])) end parsed['bufs'] = parsed['bufs'][1] + --- @param v any return vim.tbl_map(function(v) if #v == 0 then return nil @@ -931,10 +1003,12 @@ end function module.add_builddir_to_rtp() -- Add runtime from build dir for doc/tags (used with :help). - module.command(string.format([[set rtp+=%s/runtime]], module.test_build_dir)) + module.command(string.format([[set rtp+=%s/runtime]], module.paths.test_build_dir)) end --- Kill (reap) a process by PID. +--- Kill (reap) a process by PID. +--- @param pid string +--- @return boolean? function module.os_kill(pid) return os.execute( ( @@ -944,7 +1018,9 @@ function module.os_kill(pid) ) end --- Create folder with non existing parents +--- Create folder with non existing parents +--- @param path string +--- @return boolean? function module.mkdir_p(path) return os.execute((is_os('win') and 'mkdir ' .. path or 'mkdir -p ' .. path)) end diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 59a9283868..e4810feedb 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -219,7 +219,7 @@ describe('prompt buffer', function() eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) end) - -- oldtest: Test_prompt_close_modify_hidden() + -- oldtest: Test_prompt_leave_modify_hidden() it('modifying hidden buffer does not prevent prompt buffer mode change', function() source([[ file hidden @@ -228,14 +228,26 @@ describe('prompt buffer', function() new prompt set buftype=prompt + inoremap <buffer> w <Cmd>wincmd w<CR> inoremap <buffer> q <Cmd>bwipe!<CR> - autocmd BufWinLeave prompt call setbufline('hidden', 1, 'Test') + autocmd BufLeave prompt call appendbufline('hidden', '$', 'Leave') + autocmd BufEnter prompt call appendbufline('hidden', '$', 'Enter') + autocmd BufWinLeave prompt call appendbufline('hidden', '$', 'Close') ]]) feed('a') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) + feed('w') + eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) + feed('<C-W>w') + eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) feed('q') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) command('bwipe!') - expect('Test') + expect([[ + + Leave + Enter + Leave + Close]]) end) end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 66ba0f71f2..6821fe3c5e 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -6,8 +6,8 @@ local eq = helpers.eq local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local nvim_dir = helpers.nvim_dir -local test_build_dir = helpers.test_build_dir -local test_source_path = helpers.test_source_path +local test_build_dir = helpers.paths.test_build_dir +local test_source_path = helpers.paths.test_source_path local nvim_prog = helpers.nvim_prog local is_os = helpers.is_os local mkdir = helpers.mkdir diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 6ed7af6b6e..171d37ba55 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -162,7 +162,7 @@ describe('luaeval()', function() return sp('map', '[' .. val .. ']') end local function luaevalarg(argexpr, expr) - return eval(([=[ + return eval((([=[ [ extend(g:, {'_ret': luaeval(%s, %s)})._ret, type(g:_ret)==type({})&&has_key(g:_ret, '_TYPE') @@ -172,7 +172,7 @@ describe('luaeval()', function() get(g:_ret, '_VAL', g:_ret) ] : [0, g:_ret]][1] - ]=]):format(expr or '"_A"', argexpr):gsub('\n', '')) + ]=]):format(expr or '"_A"', argexpr):gsub('\n', ''))) end it('correctly passes special dictionaries', function() diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index 106d40fe0e..044d707499 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -99,7 +99,10 @@ describe('vim._watch', function() describe('poll', function() it('detects file changes', function() - skip(is_os('bsd'), 'bsd only reports rename on folders if file inside change') + skip( + is_os('bsd'), + 'kqueue only reports events on watched folder itself, not contained files #26110' + ) local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX') local result = exec_lua( diff --git a/test/functional/options/chars_spec.lua b/test/functional/options/chars_spec.lua index 64de25112a..e9c20b5da9 100644 --- a/test/functional/options/chars_spec.lua +++ b/test/functional/options/chars_spec.lua @@ -1,9 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, command = helpers.clear, helpers.command +local pcall_err = helpers.pcall_err local eval = helpers.eval local eq = helpers.eq -local exc_exec = helpers.exc_exec local insert = helpers.insert local feed = helpers.feed local api = helpers.api @@ -17,11 +17,6 @@ describe("'fillchars'", function() screen:attach() end) - local function shouldfail(val, errval) - errval = errval or val - eq('Vim(set):E474: Invalid argument: fillchars=' .. errval, exc_exec('set fillchars=' .. val)) - end - describe('"eob" flag', function() it("uses '~' by default", function() eq('', eval('&fillchars')) @@ -64,10 +59,22 @@ describe("'fillchars'", function() end) it('handles invalid values', function() - shouldfail('eob:') -- empty string - shouldfail('eob:馬') -- doublewidth char - shouldfail('eob:xy') -- two ascii chars - shouldfail('eob:\255', 'eob:<ff>') -- invalid UTF-8 + eq( + 'Vim(set):E1511: Wrong number of characters for field "eob": fillchars=eob:', + pcall_err(command, 'set fillchars=eob:') -- empty string + ) + eq( + 'Vim(set):E1512: Wrong character width for field "eob": fillchars=eob:馬', + pcall_err(command, 'set fillchars=eob:馬') -- doublewidth char + ) + eq( + 'Vim(set):E1511: Wrong number of characters for field "eob": fillchars=eob:xy', + pcall_err(command, 'set fillchars=eob:xy') -- two ascii chars + ) + eq( + 'Vim(set):E1512: Wrong character width for field "eob": fillchars=eob:<ff>', + pcall_err(command, 'set fillchars=eob:\255') -- invalid UTF-8 + ) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 252931eccb..b602143443 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -4261,7 +4261,10 @@ describe('LSP', function() describe('vim.lsp._watchfiles', function() it('sends notifications when files change', function() - skip(is_os('bsd'), 'bsd only reports rename on folders if file inside change') + skip( + is_os('bsd'), + 'kqueue only reports events on watched folder itself, not contained files #26110' + ) local root_dir = tmpname() os.remove(root_dir) mkdir(root_dir) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 94e0ee6e82..1b5c0eab5d 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -525,7 +525,7 @@ describe('ShaDa marks support code', function() local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do if v.type == 7 and v.value.f == mock_file_path .. '-' then - print(require('test.helpers').format_luav(v)) + print(require('test.format_string').format_luav(v)) found = found + 1 end end diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 2942beab2f..6eb318015d 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -7,7 +7,7 @@ local is_os = helpers.is_os local skip = helpers.skip local uv = vim.uv -local paths = require('test.cmakeconfig.paths') +local paths = helpers.paths local shada_helpers = require('test.functional.shada.helpers') local reset, clear, get_shada_rw = diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 2b39c93f14..9615534c87 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -151,7 +151,7 @@ describe('no crash when TermOpen autocommand', function() it('processes job exit event when using termopen()', function() command([[autocmd TermOpen * call input('')]]) - async_meths.command('terminal foobar') + async_meths.nvim_command('terminal foobar') screen:expect { grid = [[ | @@ -181,7 +181,7 @@ describe('no crash when TermOpen autocommand', function() it('wipes buffer and processes events when using termopen()', function() command([[autocmd TermOpen * bwipe! | call input('')]]) - async_meths.command('terminal foobar') + async_meths.nvim_command('terminal foobar') screen:expect { grid = [[ | @@ -202,7 +202,7 @@ describe('no crash when TermOpen autocommand', function() it('wipes buffer and processes events when using nvim_open_term()', function() command([[autocmd TermOpen * bwipe! | call input('')]]) - async_meths.open_term(0, {}) + async_meths.nvim_open_term(0, {}) screen:expect { grid = [[ | diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index f4347a460e..7e79479fc5 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -2895,3 +2895,54 @@ it("'inccommand' cannot be changed during preview #23136", function() feed(':%s/foo/bar<C-E><C-E><C-E>') assert_alive() end) + +it("'inccommand' value can be changed multiple times #27086", function() + clear() + local screen = Screen.new(30, 20) + common_setup(screen, 'split', 'foo1\nfoo2\nfoo3') + for _ = 1, 3 do + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*7 + {11:[No Name] [+] }| + |1| {12:bar}1 | + |2| {12:bar}2 | + |3| {12:bar}3 | + {15:~ }|*4 + {10:[Preview] }| + :%s/foo/bar^ | + ]]) + feed('<Esc>') + command('set inccommand=nosplit') + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*16 + :%s/foo/bar^ | + ]]) + feed('<Esc>') + command('set inccommand=split') + end +end) + +it("'inccommand' disables preview if preview buffer can't be created #27086", function() + clear() + api.nvim_buf_set_name(0, '[Preview]') + local screen = Screen.new(30, 20) + common_setup(screen, 'split', 'foo1\nfoo2\nfoo3') + eq('split', api.nvim_get_option_value('inccommand', {})) + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*16 + :%s/foo/bar^ | + ]]) + eq('nosplit', api.nvim_get_option_value('inccommand', {})) +end) diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index d30575a9f9..a714df72b7 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -1,6 +1,8 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local api = helpers.api local clear = helpers.clear +local eq = helpers.eq local exec_lua = helpers.exec_lua local insert = helpers.insert local feed = helpers.feed @@ -495,6 +497,22 @@ describe("'inccommand' for user commands", function() test_preview_break_undo() end) end) + + it('disables preview if preview buffer cannot be created #27086', function() + command('set inccommand=split') + api.nvim_buf_set_name(0, '[Preview]') + exec_lua([[ + vim.api.nvim_create_user_command('Test', function() end, { + nargs = '*', + preview = function(_, _, _) + return 2 + end + }) + ]]) + eq('split', api.nvim_get_option_value('inccommand', {})) + feed(':Test') + eq('nosplit', api.nvim_get_option_value('inccommand', {})) + end) end) describe("'inccommand' with multiple buffers", function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 4f95cd909c..9d1b6163d2 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -7,7 +7,7 @@ local command = helpers.command local set_method_error = helpers.set_method_error local api = helpers.api local async_meths = helpers.async_meths -local test_build_dir = helpers.test_build_dir +local test_build_dir = helpers.paths.test_build_dir local nvim_prog = helpers.nvim_prog local exec = helpers.exec local exec_capture = helpers.exec_capture @@ -1039,7 +1039,7 @@ stack traceback: end) it('supports nvim_echo messages with multiple attrs', function() - async_meths.echo( + async_meths.nvim_echo( { { 'wow, ', 'Search' }, { 'such\n\nvery ', 'ErrorMsg' }, { 'color', 'LineNr' } }, true, {} @@ -1403,7 +1403,7 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim end) it('supports nvim_echo messages with multiple attrs', function() - async_meths.echo( + async_meths.nvim_echo( { { 'wow, ', 'Search' }, { 'such\n\nvery ', 'ErrorMsg' }, { 'color', 'LineNr' } }, true, {} @@ -1521,7 +1521,7 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim end) it('consecutive calls to win_move_statusline() work after multiline message #21014', function() - async_meths.exec( + async_meths.nvim_exec( [[ echo "\n" call win_move_statusline(0, -4) @@ -2196,7 +2196,7 @@ aliquip ex ea commodo consequat.]] } -- not processed while command is executing - async_meths.ui_try_resize(35, 5) + async_meths.nvim_ui_try_resize(35, 5) -- TODO(bfredl): ideally it should be processed just -- before the "press ENTER" prompt though diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index b74a15d56f..1f7d187016 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -2527,7 +2527,7 @@ describe('builtin popupmenu', function() ]]) end feed('<C-E>') - async_meths.call_function('input', { '', '', 'sign' }) + async_meths.nvim_call_function('input', { '', '', 'sign' }) if multigrid then screen:expect { grid = [[ diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 29ec08e15c..af03bce4a0 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -548,4 +548,21 @@ describe('Signs', function() delete | redraw | undo ]]) end) + + it('sign not shown on line it was previously on after undo', function() + exec([[ + call setline(1, range(1, 4)) + call nvim_buf_set_extmark(0, nvim_create_namespace(''), 1, 0, {'sign_text':'S1'}) + ]]) + exec('norm 2Gdd') + exec('silent undo') + screen:expect([[ + {2: }1 | + S1^2 | + {2: }3 | + {2: }4 | + {0:~ }|*9 + | + ]]) + end) end) diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index b8f9bbc92d..dc60a474f3 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -314,13 +314,13 @@ describe('context functions', function() } local with_jumps = { - ['jumps'] = eval(([[ + ['jumps'] = eval((([[ filter(map(add( getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), 'filter( { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') - ]]):gsub('\n', '')), + ]]):gsub('\n', ''))), } local with_bufs = { diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index 6dd22078d6..b749d5a7f0 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -414,19 +414,19 @@ describe('confirm()', function() -- screen:expect() calls are needed to avoid feeding input too early screen:expect({ any = '%[No Name%]' }) - async_meths.command([[let a = confirm('Press O to proceed')]]) + async_meths.nvim_command([[let a = confirm('Press O to proceed')]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('o') screen:expect({ any = '%[No Name%]' }) eq(1, api.nvim_get_var('a')) - async_meths.command([[let a = 'Are you sure?'->confirm("&Yes\n&No")]]) + async_meths.nvim_command([[let a = 'Are you sure?'->confirm("&Yes\n&No")]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('y') screen:expect({ any = '%[No Name%]' }) eq(1, api.nvim_get_var('a')) - async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + async_meths.nvim_command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('n') screen:expect({ any = '%[No Name%]' }) @@ -435,26 +435,26 @@ describe('confirm()', function() -- Not possible to match Vim's CTRL-C test here as CTRL-C always sets got_int in Nvim. -- confirm() should return 0 when pressing ESC. - async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + async_meths.nvim_command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('<Esc>') screen:expect({ any = '%[No Name%]' }) eq(0, api.nvim_get_var('a')) -- Default choice is returned when pressing <CR>. - async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) + async_meths.nvim_command([[let a = confirm('Are you sure?', "&Yes\n&No")]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('<CR>') screen:expect({ any = '%[No Name%]' }) eq(1, api.nvim_get_var('a')) - async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No", 2)]]) + async_meths.nvim_command([[let a = confirm('Are you sure?', "&Yes\n&No", 2)]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('<CR>') screen:expect({ any = '%[No Name%]' }) eq(2, api.nvim_get_var('a')) - async_meths.command([[let a = confirm('Are you sure?', "&Yes\n&No", 0)]]) + async_meths.nvim_command([[let a = confirm('Are you sure?', "&Yes\n&No", 0)]]) screen:expect({ any = '{CONFIRM:.+: }' }) feed('<CR>') screen:expect({ any = '%[No Name%]' }) @@ -462,7 +462,9 @@ describe('confirm()', function() -- Test with the {type} 4th argument for _, type in ipairs({ 'Error', 'Question', 'Info', 'Warning', 'Generic' }) do - async_meths.command(([[let a = confirm('Are you sure?', "&Yes\n&No", 1, '%s')]]):format(type)) + async_meths.nvim_command( + ([[let a = confirm('Are you sure?', "&Yes\n&No", 1, '%s')]]):format(type) + ) screen:expect({ any = '{CONFIRM:.+: }' }) feed('y') screen:expect({ any = '%[No Name%]' }) @@ -518,7 +520,7 @@ describe('confirm()', function() feed(':call nvim_command("edit x")<cr>') check_and_clear(':call nvim_command("edit |\n') - async_meths.command('edit x') + async_meths.nvim_command('edit x') check_and_clear(' |\n') end) end) diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index 3f53c21e7a..046d451888 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local feed, eq, eval, ok = helpers.feed, helpers.eq, helpers.eval, helpers.ok -local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run +local source, async_meths, run = helpers.source, helpers.async_meths, helpers.run local clear, command, fn = helpers.clear, helpers.command, helpers.fn local exc_exec = helpers.exc_exec local api = helpers.api @@ -52,9 +52,9 @@ describe('timers', function() endfunc ]]) eval("timer_start(10, 'MyHandler', {'repeat': -1})") - nvim_async('command', 'sleep 10') + async_meths.nvim_command('sleep 10') eq(-1, eval('g:val')) -- timer did nothing yet. - nvim_async('command', 'let g:val = 0') + async_meths.nvim_command('let g:val = 0') run(nil, nil, nil, load_adjust(20)) retry(nil, nil, function() eq(2, eval('g:val')) @@ -70,7 +70,7 @@ describe('timers', function() end) it('can be started during sleep', function() - nvim_async('command', 'sleep 10') + async_meths.nvim_command('sleep 10') -- this also tests that remote requests works during sleep eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 2}), g:val][1]")) run(nil, nil, nil, load_adjust(20)) @@ -94,7 +94,7 @@ describe('timers', function() it('are triggered in blocking getchar() call', function() command("call timer_start(5, 'MyHandler', {'repeat': -1})") - nvim_async('command', 'let g:val = 0 | let g:c = getchar()') + async_meths.nvim_command('let g:val = 0 | let g:c = getchar()') retry(nil, nil, function() local val = eval('g:val') ok(val >= 2, '>= 2', tostring(val)) @@ -128,8 +128,10 @@ describe('timers', function() redraw endfunc ]]) - nvim_async('command', 'let g:c2 = getchar()') - nvim_async('command', 'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})") + async_meths.nvim_command('let g:c2 = getchar()') + async_meths.nvim_command( + 'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})" + ) screen:expect([[ ^ITEM 1 | @@ -137,7 +139,7 @@ describe('timers', function() {1:~ }|*3 | ]]) - nvim_async('command', 'let g:cont = 1') + async_meths.nvim_command('let g:cont = 1') screen:expect([[ ^ITEM 1 | diff --git a/test/helpers.lua b/test/helpers.lua index 83d7a0ece6..24ccead8b2 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -18,15 +18,14 @@ end --- @class test.helpers local module = { - REMOVE_THIS = {}, + paths = Paths, } --- @param p string --- @return string local function relpath(p) p = vim.fs.normalize(p) - local cwd = uv.cwd() - return p:gsub('^' .. cwd) + return (p:gsub('^' .. uv.cwd, '')) end --- @param path string @@ -42,41 +41,34 @@ function module.isdir(path) return stat.type == 'directory' end ---- @param path string ---- @return boolean -function module.isfile(path) - if not path then - return false - end - local stat = uv.fs_stat(path) - if not stat then - return false - end - return stat.type == 'file' -end - +--- @param ... string|string[] --- @return string function module.argss_to_cmd(...) - local cmd = '' + local cmd = {} --- @type string[] for i = 1, select('#', ...) do local arg = select(i, ...) if type(arg) == 'string' then - cmd = cmd .. ' ' .. shell_quote(arg) + cmd[#cmd + 1] = shell_quote(arg) else + --- @cast arg string[] for _, subarg in ipairs(arg) do - cmd = cmd .. ' ' .. shell_quote(subarg) + cmd[#cmd + 1] = shell_quote(subarg) end end end - return cmd + return table.concat(cmd, ' ') end function module.popen_r(...) return io.popen(module.argss_to_cmd(...), 'r') end --- Calls fn() until it succeeds, up to `max` times or until `max_ms` --- milliseconds have passed. +--- Calls fn() until it succeeds, up to `max` times or until `max_ms` +--- milliseconds have passed. +--- @param max integer? +--- @param max_ms integer? +--- @param fn function +--- @return any function module.retry(max, max_ms, fn) luaassert(max == nil or max > 0) luaassert(max_ms == nil or max_ms > 0) @@ -84,6 +76,7 @@ function module.retry(max, max_ms, fn) local timeout = (max_ms and max_ms or 10000) local start_time = uv.now() while true do + --- @type boolean, any local status, result = pcall(fn) if status then return result @@ -133,6 +126,9 @@ function module.fail(msg) return luaassert.epicfail(msg) end +--- @param pat string +--- @param actual string +--- @return boolean function module.matches(pat, actual) if nil ~= string.match(actual, pat) then return true @@ -182,10 +178,16 @@ end --- Asserts that `pat` does NOT match any line in the tail of `logfile`. --- --- @see assert_log +--- @param pat (string) Lua pattern to match lines in the log file +--- @param logfile? (string) Full path to log file (default=$NVIM_LOG_FILE) +--- @param nrlines? (number) Search up to this many log lines function module.assert_nolog(pat, logfile, nrlines) return module.assert_log(pat, logfile, nrlines, true) end +--- @param fn fun(...): any +--- @param ... any +--- @return boolean, any function module.pcall(fn, ...) luaassert(type(fn) == 'function') local status, rv = pcall(fn, ...) @@ -233,6 +235,8 @@ end -- -- Match Lua pattern. -- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) -- +--- @param fn function +--- @return string function module.pcall_err_withfile(fn, ...) luaassert(type(fn) == 'function') local status, rv = module.pcall(fn, ...) @@ -242,19 +246,29 @@ function module.pcall_err_withfile(fn, ...) return rv end +--- @param fn function +--- @param ... any +--- @return string function module.pcall_err_withtrace(fn, ...) local errmsg = module.pcall_err_withfile(fn, ...) - return errmsg - :gsub('^%.%.%./helpers%.lua:0: ', '') - :gsub('^Error executing lua:- ', '') - :gsub('^%[string "<nvim>"%]:0: ', '') + return ( + errmsg + :gsub('^%.%.%./helpers%.lua:0: ', '') + :gsub('^Error executing lua:- ', '') + :gsub('^%[string "<nvim>"%]:0: ', '') + ) end -function module.pcall_err(...) - return module.remove_trace(module.pcall_err_withtrace(...)) +--- @param fn function +--- @param ... any +--- @return string +function module.pcall_err(fn, ...) + return module.remove_trace(module.pcall_err_withtrace(fn, ...)) end +--- @param s string +--- @return string function module.remove_trace(s) return (s:gsub('\n%s*stack traceback:.*', '')) end @@ -264,9 +278,9 @@ end -- exc_re: exclude pattern(s) (string or table) function module.glob(initial_path, re, exc_re) exc_re = type(exc_re) == 'table' and exc_re or { exc_re } - local paths_to_check = { initial_path } - local ret = {} - local checked_files = {} + local paths_to_check = { initial_path } --- @type string[] + local ret = {} --- @type string[] + local checked_files = {} --- @type table<string,true> local function is_excluded(path) for _, pat in pairs(exc_re) do if path:match(pat) then @@ -313,7 +327,7 @@ function module.check_logs() local file = log_dir .. '/' .. tail local fd = assert(io.open(file)) local start_msg = ('='):rep(20) .. ' File ' .. file .. ' ' .. ('='):rep(20) - local lines = {} + local lines = {} --- @type string[] local warning_line = 0 for line in fd:lines() do local cur_warning_line = check_logs_useless_lines[line] @@ -325,6 +339,7 @@ function module.check_logs() end fd:close() if #lines > 0 then + --- @type boolean?, file*? local status, f local out = io.stdout if os.getenv('SYMBOLIZER') then @@ -332,6 +347,7 @@ function module.check_logs() end out:write(start_msg .. '\n') if status then + assert(f) for line in f:lines() do out:write('= ' .. line .. '\n') end @@ -353,10 +369,7 @@ function module.check_logs() end function module.sysname() - local platform = uv.os_uname() - if platform and platform.sysname then - return platform.sysname:lower() - end + return uv.os_uname().sysname:lower() end function module.is_os(s) @@ -372,26 +385,15 @@ function module.is_os(s) ) end -function module.is_arch(s) - local machine = uv.os_uname().machine - if s == 'arm64' or s == 'aarch64' then - return machine == 'arm64' or machine == 'aarch64' - end - - if s == 'x86' or s == 'x86_64' or s == 'amd64' then - return machine == 'x86_64' - end - - return machine == s -end - local function tmpdir_get() return os.getenv('TMPDIR') and os.getenv('TMPDIR') or os.getenv('TEMP') end --- Is temp directory `dir` defined local to the project workspace? +--- Is temp directory `dir` defined local to the project workspace? +--- @param dir string? +--- @return boolean local function tmpdir_is_local(dir) - return not not (dir and string.find(dir, 'Xtest')) + return not not (dir and dir:find('Xtest')) end --- Creates a new temporary file for use by tests. @@ -422,14 +424,6 @@ module.tmpname = (function() end end)() -function module.hasenv(name) - local env = os.getenv(name) - if env and env ~= '' then - return env - end - return nil -end - local function deps_prefix() local env = os.getenv('DEPS_PREFIX') return (env and env ~= '') and env or '.deps/usr' @@ -443,6 +437,7 @@ function module.check_cores(app, force) -- luacheck: ignore return end app = app or 'build/bin/nvim' -- luacheck: ignore + --- @type string, string?, string[] local initial_path, re, exc_re local gdb_db_cmd = 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' @@ -455,13 +450,14 @@ function module.check_cores(app, force) -- luacheck: ignore and relpath(tmpdir_get()):gsub('^[ ./]+', ''):gsub('%/+$', ''):gsub('([^%w])', '%%%1') or nil ) - local db_cmd - if module.hasenv('NVIM_TEST_CORE_GLOB_DIRECTORY') then - initial_path = os.getenv('NVIM_TEST_CORE_GLOB_DIRECTORY') + local db_cmd --- @type string + local test_glob_dir = os.getenv('NVIM_TEST_CORE_GLOB_DIRECTORY') + if test_glob_dir and test_glob_dir ~= '' then + initial_path = test_glob_dir re = os.getenv('NVIM_TEST_CORE_GLOB_RE') exc_re = { os.getenv('NVIM_TEST_CORE_EXC_RE'), local_tmpdir } db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd - random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') + random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') ~= '' elseif module.is_os('mac') then initial_path = '/cores' re = nil @@ -519,21 +515,28 @@ function module.repeated_read_cmd(...) return nil end +--- @generic T +--- @param orig T +--- @return T function module.shallowcopy(orig) if type(orig) ~= 'table' then return orig end - local copy = {} + --- @cast orig table<any,any> + local copy = {} --- @type table<any,any> for orig_key, orig_value in pairs(orig) do copy[orig_key] = orig_value end return copy end +--- @param d1 table<any,any> +--- @param d2 table<any,any> +--- @return table<any,any> function module.mergedicts_copy(d1, d2) local ret = module.shallowcopy(d1) for k, v in pairs(d2) do - if d2[k] == module.REMOVE_THIS then + if d2[k] == vim.NIL then ret[k] = nil elseif type(d1[k]) == 'table' and type(v) == 'table' then ret[k] = module.mergedicts_copy(d1[k], v) @@ -544,16 +547,18 @@ function module.mergedicts_copy(d1, d2) return ret end --- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 --- --- Note: does not do copies of d2 values used. +--- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 +--- +--- Note: does not do copies of d2 values used. +--- @param d1 table<any,any> +--- @param d2 table<any,any> function module.dictdiff(d1, d2) - local ret = {} + local ret = {} --- @type table<any,any> local hasdiff = false for k, v in pairs(d1) do if d2[k] == nil then hasdiff = true - ret[k] = module.REMOVE_THIS + ret[k] = vim.NIL elseif type(v) == type(d2[k]) then if type(v) == 'table' then local subdiff = module.dictdiff(v, d2[k]) @@ -584,17 +589,11 @@ function module.dictdiff(d1, d2) end end -function module.updated(d, d2) - for k, v in pairs(d2) do - d[k] = v - end - return d -end - -- Concat list-like tables. function module.concat_tables(...) - local ret = {} + local ret = {} --- @type table<any,any> for i = 1, select('#', ...) do + --- @type table<any,any> local tbl = select(i, ...) if tbl then for _, v in ipairs(tbl) do @@ -633,192 +632,13 @@ function module.dedent(str, leave_indent) return str end -local function format_float(v) - -- On windows exponent appears to have three digits and not two - local ret = ('%.6e'):format(v) - local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') - return l .. '.' .. f .. 'e' .. es .. e -end - -local SUBTBL = { - '\\000', - '\\001', - '\\002', - '\\003', - '\\004', - '\\005', - '\\006', - '\\007', - '\\008', - '\\t', - '\\n', - '\\011', - '\\012', - '\\r', - '\\014', - '\\015', - '\\016', - '\\017', - '\\018', - '\\019', - '\\020', - '\\021', - '\\022', - '\\023', - '\\024', - '\\025', - '\\026', - '\\027', - '\\028', - '\\029', - '\\030', - '\\031', -} - --- Formats Lua value `v`. --- --- TODO(justinmk): redundant with vim.inspect() ? --- --- "Nice table formatting similar to screen:snapshot_util()". --- Commit: 520c0b91a528 -function module.format_luav(v, indent, opts) - opts = opts or {} - local linesep = '\n' - local next_indent_arg = nil - local indent_shift = opts.indent_shift or ' ' - local next_indent - local nl = '\n' - if indent == nil then - indent = '' - linesep = '' - next_indent = '' - nl = ' ' - else - next_indent_arg = indent .. indent_shift - next_indent = indent .. indent_shift - end - local ret = '' - if type(v) == 'string' then - if opts.literal_strings then - ret = v - else - local quote = opts.dquote_strings and '"' or "'" - ret = quote - .. tostring(v) - :gsub(opts.dquote_strings and '["\\]' or "['\\]", '\\%0') - :gsub('[%z\1-\31]', function(match) - return SUBTBL[match:byte() + 1] - end) - .. quote - end - elseif type(v) == 'table' then - if v == module.REMOVE_THIS then - ret = 'REMOVE_THIS' - else - local processed_keys = {} - ret = '{' .. linesep - local non_empty = false - local format_luav = module.format_luav - for i, subv in ipairs(v) do - ret = ('%s%s%s,%s'):format(ret, next_indent, format_luav(subv, next_indent_arg, opts), nl) - processed_keys[i] = true - non_empty = true - end - for k, subv in pairs(v) do - if not processed_keys[k] then - if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then - ret = ret .. next_indent .. k .. ' = ' - else - ret = ('%s%s[%s] = '):format(ret, next_indent, format_luav(k, nil, opts)) - end - ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl - non_empty = true - end - end - if nl == ' ' and non_empty then - ret = ret:sub(1, -3) - end - ret = ret .. indent .. '}' - end - elseif type(v) == 'number' then - if v % 1 == 0 then - ret = ('%d'):format(v) - else - ret = format_float(v) - end - elseif type(v) == 'nil' then - ret = 'nil' - elseif type(v) == 'boolean' then - ret = (v and 'true' or 'false') - else - print(type(v)) - -- Not implemented yet - luaassert(false) - end - return ret -end - --- Like Python repr(), "{!r}".format(s) --- --- Commit: 520c0b91a528 -function module.format_string(fmt, ...) - local i = 0 - local args = { ... } - local function getarg() - i = i + 1 - return args[i] - end - local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) - local subfmt = match:gsub('%*', function() - return tostring(getarg()) - end) - local arg = nil - if subfmt:sub(-1) ~= '%' then - arg = getarg() - end - if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then - -- %r is like built-in %q, but it is supposed to single-quote strings and - -- not double-quote them, and also work not only for strings. - -- Builtin %q is replaced here as it gives invalid and inconsistent with - -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`, - -- lua leaves as-is. - arg = module.format_luav(arg, nil, { dquote_strings = (subfmt:sub(-1) == 'q') }) - subfmt = subfmt:sub(1, -2) .. 's' - end - if subfmt == '%e' then - return format_float(arg) - else - return subfmt:format(arg) - end - end) - return ret -end - function module.intchar2lua(ch) ch = tonumber(ch) return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch end -local fixtbl_metatable = { - __newindex = function() - luaassert(false) - end, -} - -function module.fixtbl(tbl) - return setmetatable(tbl, fixtbl_metatable) -end - -function module.fixtbl_rec(tbl) - local fixtbl_rec = module.fixtbl_rec - for _, v in pairs(tbl) do - if type(v) == 'table' then - fixtbl_rec(v) - end - end - return module.fixtbl(tbl) -end - +--- @param str string +--- @return string function module.hexdump(str) local len = string.len(str) local dump = '' @@ -844,10 +664,10 @@ function module.hexdump(str) return dump .. hex .. string.rep(' ', 8 - len % 8) .. asc end --- Reads text lines from `filename` into a table. --- --- filename: path to file --- start: start line (1-indexed), negative means "lines before end" (tail) +--- Reads text lines from `filename` into a table. +--- @param filename string path to file +--- @param start? integer start line (1-indexed), negative means "lines before end" (tail) +--- @return string[]? function module.read_file_list(filename, start) local lnum = (start ~= nil and type(start) == 'number') and start or 1 local tail = (lnum < 0) @@ -883,9 +703,9 @@ function module.read_file_list(filename, start) return lines end --- Reads the entire contents of `filename` into a string. --- --- filename: path to file +--- Reads the entire contents of `filename` into a string. +--- @param filename string +--- @return string? function module.read_file(filename) local file = io.open(filename, 'r') if not file then @@ -901,6 +721,7 @@ function module.write_file(name, text, no_dedent, append) local file = assert(io.open(name, (append and 'a' or 'w'))) if type(text) == 'table' then -- Byte blob + --- @type string[] local bytes = text text = '' for _, char in ipairs(bytes) do @@ -914,6 +735,8 @@ function module.write_file(name, text, no_dedent, append) file:close() end +--- @param name? 'cirrus'|'github' +--- @return boolean function module.is_ci(name) local any = (name == nil) luaassert(any or name == 'github' or name == 'cirrus') @@ -946,12 +769,10 @@ function module.read_nvim_log(logfile, ci_rename) end --- @param path string ---- @return string +--- @return boolean? function module.mkdir(path) -- 493 is 0755 in decimal - return uv.fs_mkdir(path, 493) + return (uv.fs_mkdir(path, 493)) end -module = vim.tbl_extend('error', module, Paths, require('test.deprecated')) - return module diff --git a/test/old/testdir/test_display.vim b/test/old/testdir/test_display.vim index bd90287400..70029dcf3c 100644 --- a/test/old/testdir/test_display.vim +++ b/test/old/testdir/test_display.vim @@ -253,12 +253,12 @@ func Test_eob_fillchars() " default value " call assert_match('eob:\~', &fillchars) " invalid values - call assert_fails(':set fillchars=eob:', 'E474:') - call assert_fails(':set fillchars=eob:xy', 'E474:') - call assert_fails(':set fillchars=eob:\255', 'E474:') - call assert_fails(':set fillchars=eob:<ff>', 'E474:') - call assert_fails(":set fillchars=eob:\x01", 'E474:') - call assert_fails(':set fillchars=eob:\\x01', 'E474:') + call assert_fails(':set fillchars=eob:', 'E1511:') + call assert_fails(':set fillchars=eob:xy', 'E1511:') + call assert_fails(':set fillchars=eob:\255', 'E1511:') + call assert_fails(':set fillchars=eob:<ff>', 'E1511:') + call assert_fails(":set fillchars=eob:\x01", 'E1512:') + call assert_fails(':set fillchars=eob:\\x01', 'E1512:') " default is ~ new redraw @@ -412,14 +412,16 @@ func Run_Test_display_lastline(euro) call StopVimInTerminal(buf) endfunc -func Test_display_lastline() +func Test_display_lastline_dump() CheckScreendump call Run_Test_display_lastline('') call Run_Test_display_lastline('euro_') +endfunc - call assert_fails(':set fillchars=lastline:', 'E474:') - call assert_fails(':set fillchars=lastline:〇', 'E474:') +func Test_display_lastline_fails() + call assert_fails(':set fillchars=lastline:', 'E1511:') + call assert_fails(':set fillchars=lastline:〇', 'E1512:') endfunc func Test_display_long_lastline() diff --git a/test/old/testdir/test_listchars.vim b/test/old/testdir/test_listchars.vim index 179bdfa4a0..b82b70746b 100644 --- a/test/old/testdir/test_listchars.vim +++ b/test/old/testdir/test_listchars.vim @@ -429,52 +429,52 @@ func Test_listchars_invalid() call assert_fails('set listchars=leadmultispace', 'E474:') " Too short - call assert_fails('set listchars=space:', 'E474:') - call assert_fails('set listchars=tab:x', 'E474:') - call assert_fails('set listchars=multispace:', 'E474:') - call assert_fails('set listchars=leadmultispace:', 'E474:') + call assert_fails('set listchars=space:', 'E1511:') + call assert_fails('set listchars=tab:x', 'E1511:') + call assert_fails('set listchars=multispace:', 'E1511:') + call assert_fails('set listchars=leadmultispace:', 'E1511:') " One occurrence too short - call assert_fails('set listchars=space:,space:x', 'E474:') - call assert_fails('set listchars=space:x,space:', 'E474:') - call assert_fails('set listchars=tab:x,tab:xx', 'E474:') - call assert_fails('set listchars=tab:xx,tab:x', 'E474:') - call assert_fails('set listchars=multispace:,multispace:x', 'E474:') - call assert_fails('set listchars=multispace:x,multispace:', 'E474:') - call assert_fails('set listchars=leadmultispace:,leadmultispace:x', 'E474:') - call assert_fails('set listchars=leadmultispace:x,leadmultispace:', 'E474:') + call assert_fails('set listchars=space:x,space:', 'E1511:') + call assert_fails('set listchars=space:,space:x', 'E1511:') + call assert_fails('set listchars=tab:xx,tab:x', 'E1511:') + call assert_fails('set listchars=tab:x,tab:xx', 'E1511:') + call assert_fails('set listchars=multispace:,multispace:x', 'E1511:') + call assert_fails('set listchars=multispace:x,multispace:', 'E1511:') + call assert_fails('set listchars=leadmultispace:,leadmultispace:x', 'E1511:') + call assert_fails('set listchars=leadmultispace:x,leadmultispace:', 'E1511:') " Too long - call assert_fails('set listchars=space:xx', 'E474:') - call assert_fails('set listchars=tab:xxxx', 'E474:') + call assert_fails('set listchars=space:xx', 'E1511:') + call assert_fails('set listchars=tab:xxxx', 'E1511:') " Has double-width character - call assert_fails('set listchars=space:·', 'E474:') - call assert_fails('set listchars=tab:·x', 'E474:') - call assert_fails('set listchars=tab:x·', 'E474:') - call assert_fails('set listchars=tab:xx·', 'E474:') - call assert_fails('set listchars=multispace:·', 'E474:') - call assert_fails('set listchars=multispace:xxx·', 'E474:') - call assert_fails('set listchars=leadmultispace:·', 'E474:') - call assert_fails('set listchars=leadmultispace:xxx·', 'E474:') + call assert_fails('set listchars=space:·', 'E1512:') + call assert_fails('set listchars=tab:·x', 'E1512:') + call assert_fails('set listchars=tab:x·', 'E1512:') + call assert_fails('set listchars=tab:xx·', 'E1512:') + call assert_fails('set listchars=multispace:·', 'E1512:') + call assert_fails('set listchars=multispace:xxx·', 'E1512:') + call assert_fails('set listchars=leadmultispace:·', 'E1512:') + call assert_fails('set listchars=leadmultispace:xxx·', 'E1512:') " Has control character - call assert_fails("set listchars=space:\x01", 'E474:') - call assert_fails("set listchars=tab:\x01x", 'E474:') - call assert_fails("set listchars=tab:x\x01", 'E474:') - call assert_fails("set listchars=tab:xx\x01", 'E474:') - call assert_fails("set listchars=multispace:\x01", 'E474:') - call assert_fails("set listchars=multispace:xxx\x01", 'E474:') - call assert_fails('set listchars=space:\\x01', 'E474:') - call assert_fails('set listchars=tab:\\x01x', 'E474:') - call assert_fails('set listchars=tab:x\\x01', 'E474:') - call assert_fails('set listchars=tab:xx\\x01', 'E474:') - call assert_fails('set listchars=multispace:\\x01', 'E474:') - call assert_fails('set listchars=multispace:xxx\\x01', 'E474:') - call assert_fails("set listchars=leadmultispace:\x01", 'E474:') - call assert_fails('set listchars=leadmultispace:\\x01', 'E474:') - call assert_fails("set listchars=leadmultispace:xxx\x01", 'E474:') - call assert_fails('set listchars=leadmultispace:xxx\\x01', 'E474:') + call assert_fails("set listchars=space:\x01", 'E1512:') + call assert_fails("set listchars=tab:\x01x", 'E1512:') + call assert_fails("set listchars=tab:x\x01", 'E1512:') + call assert_fails("set listchars=tab:xx\x01", 'E1512:') + call assert_fails("set listchars=multispace:\x01", 'E1512:') + call assert_fails("set listchars=multispace:xxx\x01", 'E1512:') + call assert_fails('set listchars=space:\\x01', 'E1512:') + call assert_fails('set listchars=tab:\\x01x', 'E1512:') + call assert_fails('set listchars=tab:x\\x01', 'E1512:') + call assert_fails('set listchars=tab:xx\\x01', 'E1512:') + call assert_fails('set listchars=multispace:\\x01', 'E1512:') + call assert_fails('set listchars=multispace:xxx\\x01', 'E1512:') + call assert_fails("set listchars=leadmultispace:\x01", 'E1512:') + call assert_fails('set listchars=leadmultispace:\\x01', 'E1512:') + call assert_fails("set listchars=leadmultispace:xxx\x01", 'E1512:') + call assert_fails('set listchars=leadmultispace:xxx\\x01', 'E1512:') enew! set ambiwidth& listchars& ff& diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 33b8d55982..649d5f5c6c 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -1321,7 +1321,7 @@ func Test_listdict_index() call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[1.1] = 4'], ['E805:', 'E1012:', 'E805:']) call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: i] = [4, 5]'], ['E121:', 'E1001:', 'E121:']) call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: 3.2] = [4, 5]'], ['E805:', 'E1012:', 'E805:']) - " call CheckLegacyAndVim9Failure(['VAR t = test_unknown()', 'echo t[0]'], 'E685:') + " call CheckLegacyAndVim9Failure(['VAR t = test_unknown()', 'echo t[0]'], ['E685:', 'E909:', 'E685:']) endfunc " Test for a null list diff --git a/test/old/testdir/test_prompt_buffer.vim b/test/old/testdir/test_prompt_buffer.vim index 20daa07a3d..41e14ae427 100644 --- a/test/old/testdir/test_prompt_buffer.vim +++ b/test/old/testdir/test_prompt_buffer.vim @@ -298,9 +298,10 @@ func Test_prompt_appending_while_hidden() call StopVimInTerminal(buf) endfunc -" Modifying a hidden buffer while closing a prompt buffer should not prevent -" stopping of Insert mode. -func Test_prompt_close_modify_hidden() +" Modifying a hidden buffer while leaving a prompt buffer should not prevent +" stopping of Insert mode, and returning to the prompt buffer later should +" restore Insert mode. +func Test_prompt_leave_modify_hidden() call CanTestPromptBuffer() let script =<< trim END @@ -310,22 +311,34 @@ func Test_prompt_close_modify_hidden() new prompt set buftype=prompt + inoremap <buffer> w <Cmd>wincmd w<CR> inoremap <buffer> q <Cmd>bwipe!<CR> - autocmd BufWinLeave prompt call setbufline('hidden', 1, 'Test') + autocmd BufLeave prompt call appendbufline('hidden', '$', 'Leave') + autocmd BufEnter prompt call appendbufline('hidden', '$', 'Enter') + autocmd BufWinLeave prompt call appendbufline('hidden', '$', 'Close') END - call writefile(script, 'XpromptCloseModifyHidden', 'D') + call writefile(script, 'XpromptLeaveModifyHidden', 'D') - let buf = RunVimInTerminal('-S XpromptCloseModifyHidden', {'rows': 10}) + let buf = RunVimInTerminal('-S XpromptLeaveModifyHidden', {'rows': 10}) call TermWait(buf) call term_sendkeys(buf, "a") call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + call term_sendkeys(buf, "w") + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "\<C-W>w") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + call term_sendkeys(buf, "q") call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) call term_sendkeys(buf, ":bwipe!\<CR>") - call WaitForAssert({-> assert_equal('Test', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('Leave', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('Enter', term_getline(buf, 3))}) + call WaitForAssert({-> assert_equal('Leave', term_getline(buf, 4))}) + call WaitForAssert({-> assert_equal('Close', term_getline(buf, 5))}) call StopVimInTerminal(buf) endfunc diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 47499d98fa..ab4a59cfdb 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -2,8 +2,8 @@ local ffi = require('ffi') local formatc = require('test.unit.formatc') local Set = require('test.unit.set') local Preprocess = require('test.unit.preprocess') -local Paths = require('test.cmakeconfig.paths') local global_helpers = require('test.helpers') +local paths = global_helpers.paths local assert = require('luassert') local say = require('say') @@ -15,7 +15,7 @@ local eq = global_helpers.eq local trim = vim.trim -- add some standard header locations -for _, p in ipairs(Paths.include_paths) do +for _, p in ipairs(paths.include_paths) do Preprocess.add_to_include_path(p) end @@ -728,7 +728,7 @@ local function check_child_err(rd) --- @type string err = err .. '\nNo end of trace occurred' end - local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) + local cc_err, cc_emsg = pcall(check_cores, paths.test_luajit_prg, true) if not cc_err then --- @type string err = err .. '\ncheck_cores failed: ' .. cc_emsg @@ -749,7 +749,7 @@ local function itp_parent(rd, pid, allow_failure, location) io.stderr:write('Errorred out (' .. status .. '):\n' .. tostring(emsg) .. '\n') os.execute([[ sh -c "source ci/common/test.sh - check_core_dumps --delete \"]] .. Paths.test_luajit_prg .. [[\""]]) + check_core_dumps --delete \"]] .. paths.test_luajit_prg .. [[\""]]) else error(tostring(emsg) .. '\nexit code: ' .. status) end @@ -797,7 +797,7 @@ local function gen_itp(it) end local function cppimport(path) - return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path) + return cimport(paths.test_source_path .. '/test/includes/pre/' .. path) end cimport( diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index ae12136a5c..c7d3f8532f 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -14,8 +14,8 @@ local ffi = helpers.ffi local neq = helpers.neq local eq = helpers.eq local mergedicts_copy = helpers.mergedicts_copy -local format_string = helpers.format_string -local format_luav = helpers.format_luav +local format_string = require('test.format_string').format_string +local format_luav = require('test.format_string').format_luav local intchar2lua = helpers.intchar2lua local dictdiff = helpers.dictdiff diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua index a10e1098b5..aa2bf740de 100644 --- a/test/unit/viml/expressions/parser_tests.lua +++ b/test/unit/viml/expressions/parser_tests.lua @@ -1,6 +1,4 @@ -local global_helpers = require('test.helpers') - -local REMOVE_THIS = global_helpers.REMOVE_THIS +local REMOVE_THIS = vim.NIL return function(itp, _check_parsing, hl, fmtn) local function check_parsing(...) |