diff options
162 files changed, 3435 insertions, 996 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index bb7fbeb78b..d5809c42cf 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -28,7 +28,7 @@ tasks: gmake deps - build: | cd neovim - gmake CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim + gmake CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim - functionaltest: | cd neovim gmake functionaltest diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 2f0f970dcb..0ffc8aa786 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -28,16 +28,16 @@ tasks: mkdir neovim/.deps cd neovim/.deps cmake -G Ninja ../third-party/ - cmake --build . --config Debug + cmake --build . --config RelWithDebInfo - build: | mkdir neovim/build cd neovim/build cmake -G Ninja $CMAKE_EXTRA_FLAGS .. - cmake --build . --config Debug + cmake --build . --config RelWithDebInfo ./bin/nvim --version - functionaltest: | cd neovim/build - cmake --build . --config Debug --target functionaltest + cmake --build . --config RelWithDebInfo --target functionaltest - oldtest: | cd neovim gmake oldtest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3653f04d5d..44a911b21b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,12 @@ jobs: - name: Install brew packages if: matrix.os == 'osx' run: | + # Workaround brew issues rm -f /usr/local/bin/2to3 + brew unlink gcc@8 gcc@9 brew update >/dev/null - brew install automake ccache cpanminus ninja brew upgrade + brew install automake ccache perl cpanminus ninja - name: Setup interpreter packages run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 506174971a..753142e555 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,7 +76,9 @@ jobs: - name: Install brew packages run: | rm -f /usr/local/bin/2to3 + brew unlink gcc@8 gcc@9 brew update >/dev/null + brew upgrade brew install automake ninja - name: Build release run: | @@ -154,7 +156,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - delete_release: false + delete_release: '' tag_name: nightly - uses: meeDamian/github-release@2.0 with: diff --git a/.gitignore b/.gitignore index ab301bd336..6004101cce 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ compile_commands.json /.deps/ /tmp/ /.clangd/ +/.cache/clangd/ .DS_Store *.mo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c179db0c46..64433e67e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,7 @@ Developer guidelines make distclean make # Nvim build system uses ninja automatically, if available. ``` +- [Improve documentation][wiki-contribute-help] Pull requests (PRs) --------------------- @@ -235,3 +236,4 @@ as context, use the `-W` argument as well. [Clang report]: https://neovim.io/doc/reports/clang/ [complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow [master error list]: https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint/errors.json +[wiki-contribute-help]: https://github.com/neovim/neovim/wiki/contribute-%3Ahelp @@ -46,7 +46,7 @@ Install from package Pre-built packages for Windows, macOS, and Linux are found on the [Releases](https://github.com/neovim/neovim/releases/) page. -[Managed packages] are in Homebrew, [Debian], [Ubuntu], [Fedora], [Arch Linux], +[Managed packages] are in [Homebrew], [Debian], [Ubuntu], [Fedora], [Arch Linux], [Gentoo], and more! Install from source @@ -137,5 +137,6 @@ Apache 2.0 license, except for contributions copied from Vim (identified by the [Fedora]: https://apps.fedoraproject.org/packages/neovim [Arch Linux]: https://www.archlinux.org/packages/?q=neovim [Gentoo]: https://packages.gentoo.org/packages/app-editors/neovim +[Homebrew]: https://formulae.brew.sh/formula/neovim <!-- vim: set tw=80: --> diff --git a/contrib/flake.nix b/contrib/flake.nix index 86e4b37cfa..d18534215c 100644 --- a/contrib/flake.nix +++ b/contrib/flake.nix @@ -3,93 +3,100 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs }: let - system = "x86_64-linux"; - legacyPkgs = nixpkgs.legacyPackages."${system}".pkgs; - pkgs = legacyPkgs; - in { - - packages."${system}" = rec { - - neovim = legacyPkgs.neovim-unwrapped.overrideAttrs(oa: { - version = "master"; - src = ../.; - - buildInputs = oa.buildInputs ++ ([ - pkgs.tree-sitter - ]); - - cmakeFlags = oa.cmakeFlags ++ [ - "-DUSE_BUNDLED=OFF" - ]; - }); - - # a development binary to help debug issues - neovim-debug = (neovim.override { - stdenv = pkgs.llvmPackages_latest.stdenv; - lua = pkgs.enableDebugging legacyPkgs.luajit; - }).overrideAttrs(oa:{ - cmakeBuildType="Debug"; - cmakeFlags = oa.cmakeFlags ++ [ - "-DMIN_LOG_LEVEL=0" - ]; - }); - - # for neovim developers, very slow - # brings development tools as well - neovim-developer = let - lib = nixpkgs.lib; - pythonEnv = legacyPkgs.python3; - luacheck = legacyPkgs.luaPackages.luacheck; + outputs = { self, nixpkgs, flake-utils }: + { + overlay = final: prev: + let + pkgs = nixpkgs.legacyPackages.${prev.system}; + in + rec { + neovim = pkgs.neovim-unwrapped.overrideAttrs (oa: { + version = "master"; + src = ../.; + + buildInputs = oa.buildInputs ++ ([ + pkgs.tree-sitter + ]); + + cmakeFlags = oa.cmakeFlags ++ [ + "-DUSE_BUNDLED=OFF" + ]; + }); + + # a development binary to help debug issues + neovim-debug = (neovim.override { + stdenv = if pkgs.stdenv.isLinux then pkgs.llvmPackages_latest.stdenv else pkgs.stdenv; + lua = pkgs.enableDebugging pkgs.luajit; + }).overrideAttrs (oa: { + cmakeBuildType = "Debug"; + cmakeFlags = oa.cmakeFlags ++ [ + "-DMIN_LOG_LEVEL=0" + ]; + }); + + # for neovim developers, very slow + # brings development tools as well + neovim-developer = + let + lib = nixpkgs.lib; + pythonEnv = pkgs.python3; + luacheck = pkgs.luaPackages.luacheck; + in + (neovim-debug.override ({ doCheck = pkgs.stdenv.isLinux; })).overrideAttrs (oa: { + cmakeFlags = oa.cmakeFlags ++ [ + "-DLUACHECK_PRG=${luacheck}/bin/luacheck" + "-DMIN_LOG_LEVEL=0" + "-DENABLE_LTO=OFF" + "-DUSE_BUNDLED=OFF" + ] ++ pkgs.stdenv.lib.optionals pkgs.stdenv.isLinux [ + # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags + # https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports + "-DCLANG_ASAN_UBSAN=ON" + ]; + + nativeBuildInputs = oa.nativeBuildInputs ++ (with pkgs; [ + pythonEnv + include-what-you-use # for scripts/check-includes.py + jq # jq for scripts/vim-patch.sh -r + doxygen + ]); + + shellHook = oa.shellHook + '' + export NVIM_PYTHON_LOG_LEVEL=DEBUG + export NVIM_LOG_FILE=/tmp/nvim.log + + export ASAN_OPTIONS="log_path=./test.log:abort_on_error=1" + export UBSAN_OPTIONS=print_stacktrace=1 + ''; + }); + }; + } // + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + overlays = [ self.overlay ]; + inherit system; + }; in - (neovim-debug.override({doCheck = true;})).overrideAttrs(oa: { - cmakeFlags = oa.cmakeFlags ++ [ - "-DLUACHECK_PRG=${luacheck}/bin/luacheck" - "-DMIN_LOG_LEVEL=0" - "-DENABLE_LTO=OFF" - "-DUSE_BUNDLED=OFF" - # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags - # https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports - "-DCLANG_ASAN_UBSAN=ON" - ]; - - nativeBuildInputs = oa.nativeBuildInputs ++ (with pkgs; [ - pythonEnv - include-what-you-use # for scripts/check-includes.py - jq # jq for scripts/vim-patch.sh -r - doxygen - ]); - - shellHook = oa.shellHook + '' - export NVIM_PYTHON_LOG_LEVEL=DEBUG - export NVIM_LOG_FILE=/tmp/nvim.log + rec { - export ASAN_OPTIONS="log_path=./test.log:abort_on_error=1" - export UBSAN_OPTIONS=print_stacktrace=1 - ''; - }); - }; + packages = with pkgs; { + inherit neovim neovim-debug neovim-developer; + }; - defaultPackage."${system}" = self.packages."${system}".neovim; + defaultPackage = pkgs.neovim; - overlay = final: prev: { - inherit (self.packages."${system}") neovim neovim-debug; - }; + apps = { + nvim = flake-utils.lib.mkApp { drv = pkgs.neovim; name = "nvim"; }; + nvim-debug = flake-utils.lib.mkApp { drv = pkgs.neovim-debug; name = "nvim"; }; + }; - apps."${system}" = let - mkApp = pkg: { - type = "app"; - program = pkg + "/bin/nvim"; - }; - in { - nvim = mkApp self.packages."${system}".neovim; - nvim-debug = mkApp self.packages."${system}".neovim-debug; - }; + defaultApp = apps.nvim; - defaultApp."${system}" = self.apps."${system}".nvim; - - devShell."${system}" = self.packages."${system}".neovim-developer; - }; + devShell = pkgs.neovim-developer; + } + ); } diff --git a/man/nvim.1 b/man/nvim.1 index 305551c7d4..ca0f41d489 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -327,7 +327,7 @@ Print version information and exit. .Sh ENVIRONMENT .Bl -tag -width Fl .It Ev NVIM_LOG_FILE -Low-level log file, usually found at ~/.local/share/nvim/log. +Low-level log file, usually found at ~/.cache/nvim/log. :help $NVIM_LOG_FILE .It Ev VIM Used to locate user files, such as init.vim. diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index b6297472c3..cf26bc3172 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -362,6 +362,10 @@ func dist#ft#FTinc() setf aspvbs elseif lines =~ "<?" setf php + " Pascal supports // comments but they're vary rarely used for file + " headers so assume POV-Ray + elseif lines =~ '^\s*\%({\|(\*\)' || lines =~? s:ft_pascal_keywords + setf pascal else call dist#ft#FTasmsyntax() if exists("b:asmsyntax") @@ -408,6 +412,9 @@ func dist#ft#FTprogress_asm() setf progress endfunc +let s:ft_pascal_comments = '^\s*\%({\|(\*\|//\)' +let s:ft_pascal_keywords = '^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>' + func dist#ft#FTprogress_pascal() if exists("g:filetype_p") exe "setf " . g:filetype_p @@ -419,8 +426,7 @@ func dist#ft#FTprogress_pascal() let lnum = 1 while lnum <= 10 && lnum < line('$') let line = getline(lnum) - if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' - \ || line =~ '^\s*{' || line =~ '^\s*(\*' + if line =~ s:ft_pascal_comments || line =~? s:ft_pascal_keywords setf pascal return elseif line !~ '^\s*$' || line =~ '^/\*' @@ -433,6 +439,19 @@ func dist#ft#FTprogress_pascal() setf progress endfunc +func dist#ft#FTpp() + if exists("g:filetype_pp") + exe "setf " . g:filetype_pp + else + let line = getline(nextnonblank(1)) + if line =~ s:ft_pascal_comments || line =~? s:ft_pascal_keywords + setf pascal + else + setf puppet + endif + endif +endfunc + func dist#ft#FTr() let max = line("$") > 50 ? 50 : line("$") diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 99d8c41dba..78a86315a3 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -137,8 +137,6 @@ function! s:put_page(page) abort setlocal modifiable setlocal noreadonly setlocal noswapfile - " git-ls-files(1) is all one keyword/tag-target - setlocal iskeyword+=(,) silent keepjumps %delete _ silent put =a:page while getline(1) =~# '^\s*$' diff --git a/runtime/compiler/zsh.vim b/runtime/compiler/zsh.vim new file mode 100644 index 0000000000..5703c1fc44 --- /dev/null +++ b/runtime/compiler/zsh.vim @@ -0,0 +1,23 @@ +" Vim compiler file +" Compiler: Zsh +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2020 Sep 6 + +if exists("current_compiler") + finish +endif +let current_compiler = "zsh" + +if exists(":CompilerSet") != 2 " older Vim always used :setlocal + command -nargs=* CompilerSet setlocal <args> +endif + +let s:cpo_save = &cpo +set cpo&vim + +CompilerSet makeprg=zsh\ -n\ --\ %:S +CompilerSet errorformat=%f:\ line\ %l:\ %m, + \%-G%.%# + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 485c93b0dd..dd3469372e 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -663,6 +663,17 @@ nvim_del_var({name}) *nvim_del_var()* Parameters: ~ {name} Variable name +nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* + Echo a message. + + Parameters: ~ + {chunks} A list of [text, hl_group] arrays, each + representing a text chunk with specified + highlight. `hl_group` element can be omitted + for no highlight. + {history} if true, add to |message-history|. + {opts} Optional parameters. Reserved for future use. + nvim_err_write({str}) *nvim_err_write()* Writes a message to the Vim error buffer. Does not append "\n", the message is buffered (won't display) until a linefeed @@ -1937,8 +1948,7 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) {ns_id} Namespace id from |nvim_create_namespace()| {id} Extmark id {opts} Optional parameters. Keys: - • limit: Maximum number of marks to return - • details Whether to include the details dict + • details: Whether to include the details dict Return: ~ (row, col) tuple or empty list () if extmark id was absent @@ -2153,6 +2163,15 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) mark will only be used for the current redraw cycle, and not be permantently stored in the buffer. + • right_gravity : boolean that indicates the + direction the extmark will be shifted in when + new text is inserted (true for right, false + for left). defaults to true. + • end_right_gravity : boolean that indicates the + direction the extmark end position (if it + exists) will be shifted in when new text is + inserted (true for right, false for left). + Defaults to false. Return: ~ Id of the created/updated extmark diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 2d433a6d7a..384bdd63a4 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1600,7 +1600,7 @@ v:event Dictionary of event data for the current |autocommand|. Valid regtype Type of register as returned by |getregtype()|. visual Selection is visual (as opposed to, - e.g., via motion). + e.g., via motion). completed_item Current selected complete item on |CompleteChanged|, Is `{}` when no complete item selected. @@ -1783,7 +1783,7 @@ v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()| of msgpack types, use |is| operator. *v:null* *null-variable* -v:null Special value used to put "null" in JSON and NIL in msgpack. +v:null Special value used to put "null" in JSON and NIL in msgpack. See |json_encode()|. This value is converted to "v:null" when used as a String (e.g. in |expr5| with string concatenation operator) and to zero when used as a Number (e.g. in |expr5| @@ -4150,7 +4150,7 @@ function({name} [, {arglist}] [, {dict}]) garbagecollect([{atexit}]) *garbagecollect()* Cleanup unused |Lists| and |Dictionaries| that have circular references. - + There is hardly ever a need to invoke this function, as it is automatically done when Vim runs out of memory or is waiting for the user to press a key after 'updatetime'. Items without @@ -4934,7 +4934,7 @@ getwininfo([{winid}]) *getwininfo()* quickfix 1 if quickfix or location list window terminal 1 if a terminal window tabnr tab page number - topline first displayed buffer line + topline first displayed buffer line variables a reference to the dictionary with window-local variables width window width @@ -5074,7 +5074,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The Vim's compile-time feature-names (prefixed with "+") are not recognized because Nvim is always compiled with all possible - features. |feature-compile| + features. |feature-compile| Feature names can be: 1. Nvim version. For example the "nvim-0.2.1" feature means @@ -5099,7 +5099,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The ttyin input is a terminal (tty) ttyout output is a terminal (tty) unix Unix system. - *vim_starting* True during |startup|. + *vim_starting* True during |startup|. win32 Windows system (32 or 64 bit). win64 Windows system (64 bit). wsl WSL (Windows Subsystem for Linux) system @@ -5684,7 +5684,7 @@ json_encode({expr}) *json_encode()* |msgpack-special-dict|), values with self-referencing containers, strings which contain non-UTF-8 characters, pseudo-UTF-8 strings which contain codepoints reserved for - surrogate pairs (such strings are not valid UTF-8 strings). + surrogate pairs (such strings are not valid UTF-8 strings). Non-printable characters are converted into "\u1234" escapes or special escapes like "\t", other are dumped as-is. @@ -5847,7 +5847,7 @@ map({expr1}, {expr2}) *map()* {expr1} must be a |List| or a |Dictionary|. Replace each item in {expr1} with the result of evaluating {expr2}. {expr2} must be a |string| or |Funcref|. - + If {expr2} is a |string|, inside {expr2} |v:val| has the value of the current item. For a |Dictionary| |v:key| has the key of the current item and for a |List| |v:key| has the index of @@ -6114,8 +6114,8 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) - A list with three numbers, e.g., [23, 11, 3]. As above, but the third number gives the length of the highlight in bytes. - Entries with zero and negative line numbers are silently - ignored, as well as entries with negative column numbers and + Entries with zero and negative line numbers are silently + ignored, as well as entries with negative column numbers and lengths. The maximum number of positions is 8. @@ -7608,7 +7608,7 @@ setpos({expr}, {list}) setqflist({list} [, {action}[, {what}]]) *setqflist()* Create or replace or add to the quickfix list. - + When {what} is not present, use the items in {list}. Each item must be a dictionary. Non-dictionary items in {list} are ignored. Each dictionary item can contain the following @@ -7649,12 +7649,12 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* 'a' The items from {list} are added to the existing quickfix list. If there is no existing list, then a new list is created. - + 'r' The items from the current quickfix list are replaced with the items from {list}. This can also be used to clear the list: > :call setqflist([], 'r') -< +< 'f' All the quickfix lists in the quickfix stack are freed. @@ -8023,7 +8023,7 @@ sign_place({id}, {group}, {name}, {expr} [, {dict}]) empty string. {group} functions as a namespace for {id}, thus two groups can use the same IDs. Refer to |sign-identifier| for more information. - + {name} refers to a defined sign. {expr} refers to a buffer name or number. For the accepted values, see |bufname()|. @@ -8803,7 +8803,7 @@ system({cmd} [, {input}]) *system()* *E677* Note: Use |shellescape()| or |::S| with |expand()| or |fnamemodify()| to escape special characters in a command - argument. Newlines in {cmd} may cause the command to fail. + argument. Newlines in {cmd} may cause the command to fail. The characters in 'shellquote' and 'shellxquote' may also cause trouble. diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 17de1d8533..172821ac28 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1543,6 +1543,8 @@ tag command action ~ |:sign| :sig[n] manipulate signs |:silent| :sil[ent] run a command silently |:sleep| :sl[eep] do nothing for a few seconds +|:sleep!| :sl[eep]! do nothing for a few seconds, without the + cursor visible |:slast| :sla[st] split window and go to last file in the argument list |:smagic| :sm[agic] :substitute with 'magic' diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 6ca7b52fff..06666c3a27 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -27,9 +27,9 @@ Follow these steps to get LSP features: 1. Install the nvim-lspconfig plugin. It provides common configuration for various servers so you can get started quickly. https://github.com/neovim/nvim-lspconfig - 2. Install a language server. Try ":LspInstall <tab>" or use your system - package manager to install the relevant language server: + 2. Install a language server. A list of language servers can be found here: https://microsoft.github.io/language-server-protocol/implementors/servers/ + See individual server documentation for installation instructions. 3. Add `lua require('lspconfig').xx.setup{…}` to your init.vim, where "xx" is the name of the relevant config. See the nvim-lspconfig README for details. NOTE: Make sure to restart nvim after installing and configuring. @@ -62,20 +62,39 @@ Example config (in init.vim): > vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.vim.lsp.omnifunc') -- For plugins with an `on_attach` callback, call them here. For example: - -- require('completion').on_attach(client) + -- require('completion').on_attach() end -- An example of configuring for `sumneko_lua`, -- a language server for Lua. - -- First, you must run `:LspInstall sumneko_lua` for this to work. + + -- set the path to the sumneko installation + local system_name = "Linux" -- (Linux, macOS, or Windows) + local sumneko_root_path = '/path/to/lua-language-server' + local sumneko_binary = sumneko_root_path.."/bin/"..system_name.."/lua-language-server" + require('lspconfig').sumneko_lua.setup({ + cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua"}; -- An example of settings for an LSP server. -- For more options, see nvim-lspconfig settings = { Lua = { + runtime = { + -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim) + version = 'LuaJIT', + -- Setup your lua path + path = vim.split(package.path, ';'), + }, diagnostics = { - enable = true, - globals = { "vim" }, + -- Get the language server to recognize the `vim` global + globals = {'vim'}, + }, + workspace = { + -- Make the server aware of Neovim runtime files + library = { + [vim.fn.expand('$VIMRUNTIME/lua')] = true, + [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true, + }, }, } }, @@ -620,8 +639,9 @@ client() *vim.lsp.client* automatically escalate and force shutdown. • is_stopped() Checks whether a client is stopped. Returns: true if the client is fully stopped. - • on_attach(bufnr) Runs the on_attach function from the - client's config if it was defined. + • on_attach(client, bufnr) Runs the on_attach function from + the client's config if it was defined. Useful for + buffer-local setup. • Members • {id} (number): The id allocated to the client. @@ -659,6 +679,14 @@ get_active_clients() *vim.lsp.get_active_clients()* Return: ~ Table of |vim.lsp.client| objects + *vim.lsp.get_buffers_by_client_id()* +get_buffers_by_client_id({client_id}) + Parameters: ~ + {client_id} client id + + Return: ~ + list of buffer ids + get_client_by_id({client_id}) *vim.lsp.get_client_by_id()* Gets a client by id, or nil if the id is invalid. The returned client may not yet be fully initialized. @@ -757,8 +785,9 @@ start_client({config}) *vim.lsp.start_client()* {handlers} Map of language server method names to |lsp-handler| {settings} Map with language server specific - settings. These are returned to the language server if - requested via `workspace/configuration`. Keys are + settings. These are returned to the + language server if requested via + `workspace/configuration` . Keys are case-sensitive. {init_options} Values to pass in the initialization request as `initializationOptions` . @@ -796,7 +825,14 @@ start_client({config}) *vim.lsp.start_client()* `capabilities.offsetEncoding` was sent to it. You can only modify the `client.offset_encoding` here before - any notifications are sent. + any notifications are sent. Most + language servers expect to be sent + client specified settings after + initialization. Neovim does not make + this assumption. A + `workspace/didChangeConfiguration` + notification should be sent to the + server during on_init. {on_exit} Callback (code, signal, client_id) invoked on client exit. • code: exit code of the process @@ -1323,6 +1359,10 @@ set_signs({diagnostics}, {bufnr}, {client_id}, {sign_ns}, {opts}) {sign_ns} number|nil {opts} table Configuration for signs. Keys: • priority: Set the priority of the signs. + • severity_limit (DiagnosticSeverity): + • Limit severity of diagnostics found. + E.g. "Warning" means { "Error", + "Warning" } will be valid. *vim.lsp.diagnostic.set_underline()* set_underline({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) @@ -1340,10 +1380,14 @@ set_underline({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) Parameters: ~ {diagnostics} Diagnostic [] - {bufnr} number The buffer number - {client_id} number the client id - {diagnostic_ns} number|nil - {opts} table Currently unused. + {bufnr} number: The buffer number + {client_id} number: The client id + {diagnostic_ns} number|nil: The namespace + {opts} table: Configuration table: + • severity_limit (DiagnosticSeverity): + • Limit severity of diagnostics found. + E.g. "Warning" means { "Error", + "Warning" } will be valid. *vim.lsp.diagnostic.set_virtual_text()* set_virtual_text({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) @@ -1370,6 +1414,10 @@ set_virtual_text({diagnostics}, {bufnr}, {client_id}, {diagnostic_ns}, {opts}) before virtual text on line • spacing (number): Number of spaces to insert before virtual text + • severity_limit (DiagnosticSeverity): + • Limit severity of diagnostics found. + E.g. "Warning" means { "Error", + "Warning" } will be valid. *vim.lsp.diagnostic.show_line_diagnostics()* show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) @@ -1393,16 +1441,31 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) {client_id} number|nil the client id Return: ~ - {popup_bufnr, win_id} + table {popup_bufnr, win_id} + + +============================================================================== +Lua module: vim.lsp.handlers *lsp-handlers* + + *vim.lsp.handlers.progress_callback()* +progress_callback({_}, {_}, {params}, {client_id}) + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand ============================================================================== Lua module: vim.lsp.util *lsp-util* *vim.lsp.util.apply_text_document_edit()* -apply_text_document_edit({text_document_edit}) +apply_text_document_edit({text_document_edit}, {index}) + Applies a `TextDocumentEdit` , which is a list of changes to a + single document. + Parameters: ~ - {text_document_edit} (table) a `TextDocumentEdit` object + {text_document_edit} table: a `TextDocumentEdit` object + {index} number: Optional index of the edit, + if from a list of edits (or nil, if + not from a list) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit @@ -1582,6 +1645,9 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* See also: ~ |softtabstop| +get_progress_messages() *vim.lsp.util.get_progress_messages()* + TODO: Documentation + jump_to_location({location}) *vim.lsp.util.jump_to_location()* Jumps to a location. @@ -1603,6 +1669,19 @@ locations_to_items({locations}) *vim.lsp.util.locations_to_items()* Return: ~ (table) list of items +lookup_section({settings}, {section}) *vim.lsp.util.lookup_section()* + Helper function to return nested values in language server + settings + + Parameters: ~ + {settings} a table of language server settings + {section} a string indicating the field of the settings + table + + Return: ~ + (table or string) The value of settings accessed via + section + *vim.lsp.util.make_floating_popup_options()* make_floating_popup_options({width}, {height}, {opts}) Creates a table with sensible default options for a floating diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index a03de10a17..0bbed56662 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -622,6 +622,9 @@ vim.api.{func}({...}) *vim.api* Example: call the "nvim_get_current_line()" API function: > print(tostring(vim.api.nvim_get_current_line())) +vim.version() *vim.version* + Returns the version of the current neovim build. + vim.in_fast_event() *vim.in_fast_event()* Returns true if the code is executing as part of a "fast" event handler, where most of the API is disabled. These are low-level events @@ -1262,14 +1265,12 @@ validate({opt}) *vim.validate()* vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} => NOP (success) -< -> - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') -< -> - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') + + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') + + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') < Parameters: ~ diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e740a45bec..98e2d50f1d 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -734,6 +734,8 @@ A jump table for the options with a short description can be found at |Q_op|. eol allow backspacing over line breaks (join lines) start allow backspacing over the start of insert; CTRL-W and CTRL-U stop once at the start of insert. + nostop like start, except CTRL-W and CTRL-U do not stop at the start of + insert. When the value is empty, Vi compatible backspacing is used. @@ -742,6 +744,7 @@ A jump table for the options with a short description can be found at |Q_op|. 0 same as ":set backspace=" (Vi compatible) 1 same as ":set backspace=indent,eol" 2 same as ":set backspace=indent,eol,start" + 3 same as ":set backspace=indent,eol,nostop" *'backup'* *'bk'* *'nobackup'* *'nobk'* 'backup' 'bk' boolean (default off) @@ -2609,7 +2612,7 @@ A jump table for the options with a short description can be found at |Q_op|. when internal formatting is used. Make sure the cursor is kept in the same spot relative to the text then! The |mode()| function will return "i" or "R" in this situation. - + When the expression evaluates to non-zero Vim will fall back to using the internal format mechanism. @@ -3184,7 +3187,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'inccommand'* *'icm'* 'inccommand' 'icm' string (default "") global - + "nosplit": Shows the effects of a command incrementally, as you type. "split" : Also shows partial off-screen results in a preview window. @@ -5533,6 +5536,12 @@ A jump table for the options with a short description can be found at |Q_op|. "auto" only when there is a sign to display "auto:[1-9]" resize to accommodate multiple signs up to the given number (maximum 9), e.g. "auto:4" + "auto:[1-8]-[2-9]" + resize to accommodate multiple signs up to the + given maximum number (maximum 9) while keeping + at least the given minimum (maximum 8) fixed + space. The minimum number should always be less + than the maximum number, e.g. "auto:2-5" "no" never "yes" always "yes:[1-9]" always, with fixed space for signs up to the given @@ -6280,6 +6289,29 @@ A jump table for the options with a short description can be found at |Q_op|. attributes instead of "cterm" attributes. |highlight-guifg| Requires an ISO-8613-3 compatible terminal. + *'termpastefilter'* *'tpf'* +'termpastefilter' 'tpf' string (default: "BS,HT,ESC,DEL") + global + A comma separated list of options for specifying control characters + to be removed from the text pasted into the terminal window. The + supported values are: + + BS Backspace + + HT TAB + + FF Form feed + + ESC Escape + + DEL DEL + + C0 Other control characters, excluding Line feed and + Carriage return < ' ' + + C1 Control characters 0x80...0x9F + + *'terse'* *'noterse'* 'terse' boolean (default off) global @@ -6838,7 +6870,7 @@ A jump table for the options with a short description can be found at |Q_op|. a built-in |highlight-groups| item to be overridden by {hl} group in the window. Only built-in |highlight-groups| are supported, not syntax highlighting (use |:ownsyntax| for that). - + Highlights of vertical separators are determined by the window to the left of the separator. The 'tabline' highlight of a tabpage is decided by the last-focused window of the tabpage. Highlights of diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index d4b6324bc3..604c969c64 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -180,7 +180,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. Read Ex commands from {file} in each directory given by 'runtimepath' and/or 'packpath'. There is no error for non-existing files. - + Example: > :runtime syntax/c.vim @@ -254,7 +254,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:packl* *:packloadall* :packl[oadall][!] Load all packages in the "start" directory under each entry in 'packpath'. - + First all the directories found are added to 'runtimepath', then the plugins found in the directories are sourced. This allows for a plugin to diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 160995b440..4a99aa47bf 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1316,8 +1316,9 @@ file when reading and include: ============================================================================== Standard Paths *standard-path* -Nvim stores configuration and data in standard locations. Plugins are strongly -encouraged to follow this pattern also. Use |stdpath()| to get the paths. +Nvim stores configuration, data, and logs in standard locations. Plugins are +strongly encouraged to follow this pattern also. Use |stdpath()| to get the +paths. *base-directories* *xdg* The "base" (root) directories conform to the XDG Base Directory Specification. @@ -1342,8 +1343,8 @@ LOG FILE *$NVIM_LOG_FILE* Besides 'debug' and 'verbose', Nvim keeps a general log file for internal debugging, plugins and RPC clients. > :echo $NVIM_LOG_FILE -Usually the file is ~/.local/share/nvim/log unless that path is inaccessible -or if $NVIM_LOG_FILE was set before |startup|. +By default, the file is located at stdpath('cache')/log unless that path +is inaccessible or if $NVIM_LOG_FILE was set before |startup|. vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index b011db3dd3..23db809543 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -544,7 +544,7 @@ only supported by new versions of ctags (such as Exuberant ctags). the bar) and ;" is used to have Vi ignore the rest of the line. Example: APP file.c call cursor(3, 4)|;" v - + {field} .. A list of optional fields. Each field has the form: <Tab>{fieldname}:{value} diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 911e7b8b47..1696d3b9ba 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -195,7 +195,8 @@ query:iter_captures({node}, {bufnr}, {start_row}, {end_row}) text of the buffer. {start_row} and {end_row} can be used to limit matches inside a row range (this is typically used with root node as the node, i e to get syntax highlight matches in the current - viewport) + viewport). When omitted the start and end row values are used from + the given node. The iterator returns three values, a numeric id identifying the capture, the captured node, and metadata from any directives processing the match. diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 0a8584927e..82406898c8 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -170,7 +170,7 @@ the editor. `mouse_shape`: (To be implemented.) Some keys are missing in some modes. - + The following keys are deprecated: `hl_id`: Use `attr_id` instead. @@ -460,7 +460,7 @@ is not active. New UIs should implement |ui-linegrid| instead. ["set_scroll_region", top, bot, left, right] Define the scroll region used by `scroll` below. - + Note: ranges are end-inclusive, which is inconsistent with API conventions. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 32551815b0..5fb7c4ce50 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -46,26 +46,26 @@ CTRL-L Clears and redraws the screen. The redraw may happen ga Print the ascii value of the character under the cursor in decimal, hexadecimal and octal. Mnemonic: Get Ascii value. - + For example, when the cursor is on a 'R': <R> 82, Hex 52, Octal 122 ~ When the character is a non-standard ASCII character, but printable according to the 'isprint' option, the non-printable version is also given. - + When the character is larger than 127, the <M-x> form is also printed. For example: <~A> <M-^A> 129, Hex 81, Octal 201 ~ <p> <|~> <M-~> 254, Hex fe, Octal 376 ~ (where <p> is a special character) - + The <Nul> character in a file is stored internally as <NL>, but it will be shown as: <^@> 0, Hex 00, Octal 000 ~ - + If the character has composing characters these are also shown. The value of 'maxcombine' doesn't matter. - + If the character can be inserted as a digraph, also output the two characters that can be used to create the character: @@ -317,11 +317,11 @@ g8 Print the hex values of the bytes used in the optional. :redi[r] @{a-z}>> Append messages to register {a-z}. -:redi[r] @*> +:redi[r] @*> :redi[r] @+> Redirect messages to the selection or clipboard. For backward compatibility, the ">" after the register name can be omitted. See |quotestar| and |quoteplus|. -:redi[r] @*>> +:redi[r] @*>> :redi[r] @+>> Append messages to the selection or clipboard. :redi[r] @"> Redirect messages to the unnamed register. For @@ -489,12 +489,12 @@ gO Show a filetype-specific, navigable "outline" of the Currently works in |help| and |:Man| buffers. [N]gs *gs* *:sl* *:sleep* -:[N]sl[eep] [N] [m] Do nothing for [N] seconds, or [N] milliseconds if [m] +:[N]sl[eep] [N][m] Do nothing for [N] seconds, or [N] milliseconds if [m] was given. "gs" always uses seconds. Default is one second. > :sleep "sleep for one second :5sleep "sleep for five seconds - :sleep 100m "sleep for a hundred milliseconds + :sleep 100m "sleep for 100 milliseconds 10gs "sleep for ten seconds < Can be interrupted with CTRL-C. "gs" stands for "goto sleep". diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index b5623f4ea4..2c3ffcbe9a 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -124,7 +124,7 @@ CTRL-W CTRL-S *CTRL-W_CTRL-S* :[N]sp[lit] [++opt] [+cmd] [file] *:sp* *:split* Split current window in two. The result is two viewports on the same file. - + Make the new window N high (default is to use half the height of the current window). Reduces the current window height to create room (and others, if the 'equalalways' option is set, diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 959fe35662..53fd66c4df 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -585,6 +585,9 @@ au BufNewFile,BufRead *.fan,*.fwt setf fan " Factor au BufNewFile,BufRead *.factor setf factor +" Fennel +autocmd BufRead,BufNewFile *.fnl setf fennel + " Fetchmail RC file au BufNewFile,BufRead .fetchmailrc setf fetchmail @@ -1169,8 +1172,11 @@ au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp " Password file au BufNewFile,BufRead */etc/passwd,*/etc/passwd-,*/etc/passwd.edit,*/etc/shadow,*/etc/shadow-,*/etc/shadow.edit,*/var/backups/passwd.bak,*/var/backups/shadow.bak setf passwd -" Pascal (also *.p) -au BufNewFile,BufRead *.pas,*.pp setf pascal +" Pascal (also *.p, *.pp, *.inc) +au BufNewFile,BufRead *.pas setf pascal + +" Pascal or Puppet manifest +au BufNewFile,BufRead *.pp call dist#ft#FTpp() " Delphi or Lazarus program file au BufNewFile,BufRead *.dpr,*.lpr setf pascal @@ -1260,7 +1266,7 @@ au BufNewFile,BufRead *.pov setf pov " Povray configuration au BufNewFile,BufRead .povrayrc setf povini -" Povray, PHP or assembly +" Povray, Pascal, PHP or assembly au BufNewFile,BufRead *.inc call dist#ft#FTinc() " Printcap and Termcap @@ -1269,13 +1275,19 @@ au BufNewFile,BufRead *printcap au BufNewFile,BufRead *termcap \ let b:ptcap_type = "term" | setf ptcap -" PCCTS / ANTRL -"au BufNewFile,BufRead *.g setf antrl +" PCCTS / ANTLR +"au BufNewFile,BufRead *.g setf antlr au BufNewFile,BufRead *.g setf pccts " PPWizard au BufNewFile,BufRead *.it,*.ih setf ppwiz +" Puppet +au BufNewFile,BufRead Puppetfile setf ruby + +" Embedded Puppet +au BufNewFile,BufRead *.epp setf epuppet + " Obj 3D file format " TODO: is there a way to avoid MS-Windows Object files? au BufNewFile,BufRead *.obj setf obj @@ -1425,8 +1437,8 @@ au BufNewFile,BufRead *.rb,*.rbw setf ruby " RubyGems au BufNewFile,BufRead *.gemspec setf ruby -" Rust -au BufNewFile,BufRead *.rs setf rust +" RBS (Ruby Signature) +au BufNewFile,BufRead *.rbs setf rbs " Rackup au BufNewFile,BufRead *.ru setf ruby @@ -1440,6 +1452,9 @@ au BufNewFile,BufRead *.builder,*.rxml,*.rjs setf ruby " Rantfile and Rakefile is like Ruby au BufNewFile,BufRead [rR]antfile,*.rant,[rR]akefile,*.rake setf ruby +" Rust +au BufNewFile,BufRead *.rs setf rust + " S-lang (or shader language, or SmallLisp) au BufNewFile,BufRead *.sl setf slang @@ -1618,6 +1633,9 @@ au BufNewFile,BufRead *.mib,*.my setf mib au BufNewFile,BufRead *.hog,snort.conf,vision.conf setf hog au BufNewFile,BufRead *.rules call dist#ft#FTRules() +" SPARQL queries +au BufNewFile,BufRead *.rq,*.sparql setf sparql + " Spec (Linux RPM) au BufNewFile,BufRead *.spec setf spec @@ -1728,8 +1746,13 @@ au BufNewFile,BufRead *.tli setf tli " Telix Salt au BufNewFile,BufRead *.slt setf tsalt -" Tera Term Language -au BufRead,BufNewFile *.ttl setf teraterm +" Tera Term Language or Turtle +au BufRead,BufNewFile *.ttl + \ if getline(1) =~ '^@\?\(prefix\|base\)' | + \ setf turtle | + \ else | + \ setf teraterm | + \ endif " Terminfo au BufNewFile,BufRead *.ti setf terminfo diff --git a/runtime/ftplugin/elm.vim b/runtime/ftplugin/elm.vim new file mode 100644 index 0000000000..1e10346186 --- /dev/null +++ b/runtime/ftplugin/elm.vim @@ -0,0 +1,18 @@ +" Elm filetype plugin file +" Language: Elm +" Maintainer: Andreas Scharf <as@99n.de> +" Latest Revision: 2020-05-29 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim + +setlocal comments=s1fl:{-,mb:\ ,ex:-},:-- +setlocal commentstring=--\ %s + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 74225a558c..5d3e00d033 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -16,7 +16,11 @@ setlocal noswapfile buftype=nofile bufhidden=hide setlocal nomodified readonly nomodifiable setlocal noexpandtab tabstop=8 softtabstop=8 shiftwidth=8 setlocal wrap breakindent linebreak -setlocal iskeyword+=- + +" Parentheses and '-' for references like `git-ls-files(1)`; '@' for systemd +" pages; ':' for Perl and C++ pages. Here, I intentionally omit the locale +" specific characters matched by `@`. +setlocal iskeyword=@-@,:,a-z,A-Z,48-57,_,.,-,(,) setlocal nonumber norelativenumber setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable @@ -24,9 +28,10 @@ setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable setlocal tagfunc=man#goto_tag if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') - nnoremap <silent> <buffer> j gj - nnoremap <silent> <buffer> k gk - nnoremap <silent> <buffer> gO :call man#show_toc()<CR> + nnoremap <silent> <buffer> j gj + nnoremap <silent> <buffer> k gk + nnoremap <silent> <buffer> gO :call man#show_toc()<CR> + nnoremap <silent> <buffer> <2-LeftMouse> :Man<CR> if s:pager nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR> else diff --git a/runtime/ftplugin/zsh.vim b/runtime/ftplugin/zsh.vim index fe8efc59ab..53ce1417dd 100644 --- a/runtime/ftplugin/zsh.vim +++ b/runtime/ftplugin/zsh.vim @@ -2,7 +2,7 @@ " Language: Zsh shell script " Maintainer: Christian Brabandt <cb@256bit.org> " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2017-11-22 +" Latest Revision: 2020-09-01 " License: Vim (see :h license) " Repository: https://github.com/chrisbra/vim-zsh @@ -14,11 +14,26 @@ let b:did_ftplugin = 1 let s:cpo_save = &cpo set cpo&vim -let b:undo_ftplugin = "setl com< cms< fo<" - setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql -let b:match_words = ',\<if\>:\<elif\>:\<else\>:\<fi\>' +let b:undo_ftplugin = "setl com< cms< fo< " + +if executable('zsh') + if !has('gui_running') && executable('less') + command! -buffer -nargs=1 RunHelp silent exe '!MANPAGER= zsh -ic "autoload -Uz run-help; run-help <args> 2>/dev/null | LESS= less"' | redraw! + elseif has('terminal') + command! -buffer -nargs=1 RunHelp silent exe ':term zsh -ic "autoload -Uz run-help; run-help <args>"' + else + command! -buffer -nargs=1 RunHelp echo system('zsh -ic "autoload -Uz run-help; run-help <args> 2>/dev/null"') + endif + if !exists('current_compiler') + compiler zsh + endif + setlocal keywordprg=:RunHelp + let b:undo_ftplugin .= 'keywordprg<' +endif + +let b:match_words = '\<if\>:\<elif\>:\<else\>:\<fi\>' \ . ',\<case\>:^\s*([^)]*):\<esac\>' \ . ',\<\%(select\|while\|until\|repeat\|for\%(each\)\=\)\>:\<done\>' let b:match_skip = 's:comment\|string\|heredoc\|subst' diff --git a/runtime/indent/elm.vim b/runtime/indent/elm.vim new file mode 100644 index 0000000000..232c347c66 --- /dev/null +++ b/runtime/indent/elm.vim @@ -0,0 +1,114 @@ +" Elm indent plugin file +" Language: Elm +" Maintainer: Andreas Scharf <as@99n.de> +" Original Author: Joseph Hager <ajhager@gmail.com> +" Copyright: Joseph Hager <ajhager@gmail.com> +" License: BSD3 +" Latest Revision: 2020-05-29 + +" Only load this indent file when no other was loaded. +if exists('b:did_indent') + finish +endif +let b:did_indent = 1 + +" Local defaults +setlocal expandtab +setlocal indentexpr=GetElmIndent() +setlocal indentkeys+=0=else,0=if,0=of,0=import,0=then,0=type,0\|,0},0\],0),=-},0=in +setlocal nolisp +setlocal nosmartindent + +" Only define the function once. +if exists('*GetElmIndent') + finish +endif + +" Indent pairs +function! s:FindPair(pstart, pmid, pend) + "call search(a:pend, 'bW') + return indent(searchpair(a:pstart, a:pmid, a:pend, 'bWn', 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string\\|comment"')) +endfunction + +function! GetElmIndent() + let l:lnum = v:lnum - 1 + + " Ident 0 if the first line of the file: + if l:lnum == 0 + return 0 + endif + + let l:ind = indent(l:lnum) + let l:lline = getline(l:lnum) + let l:line = getline(v:lnum) + + " Indent if current line begins with '}': + if l:line =~? '^\s*}' + return s:FindPair('{', '', '}') + + " Indent if current line begins with 'else': + elseif l:line =~# '^\s*else\>' + if l:lline !~# '^\s*\(if\|then\)\>' + return s:FindPair('\<if\>', '', '\<else\>') + endif + + " Indent if current line begins with 'then': + elseif l:line =~# '^\s*then\>' + if l:lline !~# '^\s*\(if\|else\)\>' + return s:FindPair('\<if\>', '', '\<then\>') + endif + + " HACK: Indent lines in case with nearest case clause: + elseif l:line =~# '->' && l:line !~# ':' && l:line !~# '\\' + return indent(search('^\s*case', 'bWn')) + &shiftwidth + + " HACK: Don't change the indentation if the last line is a comment. + elseif l:lline =~# '^\s*--' + return l:ind + + " Align the end of block comments with the start + elseif l:line =~# '^\s*-}' + return indent(search('{-', 'bWn')) + + " Indent double shift after let with an empty rhs + elseif l:lline =~# '\<let\>.*\s=$' + return l:ind + 4 + &shiftwidth + + " Align 'in' with the parent let. + elseif l:line =~# '^\s*in\>' + return indent(search('^\s*let', 'bWn')) + + " Align bindings with the parent let. + elseif l:lline =~# '\<let\>' + return l:ind + 4 + + " Align bindings with the parent in. + elseif l:lline =~# '^\s*in\>' + return l:ind + + endif + + " Add a 'shiftwidth' after lines ending with: + if l:lline =~# '\(|\|=\|->\|<-\|(\|\[\|{\|\<\(of\|else\|if\|then\)\)\s*$' + let l:ind = l:ind + &shiftwidth + + " Add a 'shiftwidth' after lines starting with type ending with '=': + elseif l:lline =~# '^\s*type' && l:line =~# '^\s*=' + let l:ind = l:ind + &shiftwidth + + " Back to normal indent after comments: + elseif l:lline =~# '-}\s*$' + call search('-}', 'bW') + let l:ind = indent(searchpair('{-', '', '-}', 'bWn', 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')) + + " Ident some operators if there aren't any starting the last line. + elseif l:line =~# '^\s*\(!\|&\|(\|`\|+\||\|{\|[\|,\)=' && l:lline !~# '^\s*\(!\|&\|(\|`\|+\||\|{\|[\|,\)=' && l:lline !~# '^\s*$' + let l:ind = l:ind + &shiftwidth + + elseif l:lline ==# '' && getline(l:lnum - 1) !=# '' + let l:ind = indent(search('^\s*\S+', 'bWn')) + + endif + + return l:ind +endfunc diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0326550245..a6f118abde 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -121,6 +121,9 @@ local active_clients = {} local all_buffer_active_clients = {} local uninitialized_clients = {} +-- Tracks all buffers attached to a client. +local all_client_active_buffers = {} + --@private --- Invokes a function for each LSP client attached to the buffer {bufnr}. --- @@ -226,6 +229,7 @@ local function validate_client_config(config) on_error = { config.on_error, "f", true }; on_exit = { config.on_exit, "f", true }; on_init = { config.on_init, "f", true }; + settings = { config.settings, "t", true }; before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; flags = { config.flags, "t", true }; @@ -327,8 +331,9 @@ end --- Checks whether a client is stopped. --- Returns: true if the client is fully stopped. --- ---- - on_attach(bufnr) +--- - on_attach(client, bufnr) --- Runs the on_attach function from the client's config if it was defined. +--- Useful for buffer-local setup. --- --- - Members --- - {id} (number): The id allocated to the client. @@ -398,6 +403,10 @@ end --- --@param handlers Map of language server method names to |lsp-handler| --- +--@param settings Map with language server specific settings. These are +--- returned to the language server if requested via `workspace/configuration`. +--- Keys are case-sensitive. +--- --@param init_options Values to pass in the initialization request --- as `initializationOptions`. See `initialize` in the LSP spec. --- @@ -424,7 +433,10 @@ end --- the server may send. For example, clangd sends --- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was --- sent to it. You can only modify the `client.offset_encoding` here before ---- any notifications are sent. +--- any notifications are sent. Most language servers expect to be sent client specified settings after +--- initialization. Neovim does not make this assumption. A +--- `workspace/didChangeConfiguration` notification should be sent +--- to the server during on_init. --- --@param on_exit Callback (code, signal, client_id) invoked on client --- exit. @@ -457,6 +469,7 @@ function lsp.start_client(config) local cmd, cmd_args, offset_encoding = cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding config.flags = config.flags or {} + config.settings = config.settings or {} local client_id = next_client_id() @@ -581,12 +594,19 @@ function lsp.start_client(config) local valid_traces = { off = 'off'; messages = 'messages'; verbose = 'verbose'; } + local version = vim.version() local initialize_params = { -- The process Id of the parent process that started the server. Is null if -- the process has not been started by another process. If the parent -- process is not alive then the server should exit (see exit notification) -- its process. processId = uv.getpid(); + -- Information about the client + -- since 3.15.0 + clientInfo = { + name = "Neovim", + version = string.format("%s.%s.%s", version.major, version.minor, version.patch) + }; -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. @@ -871,9 +891,7 @@ end function lsp._text_document_did_save_handler(bufnr) bufnr = resolve_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr) - local text = once(function() - return table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n') - end) + local text = once(buf_get_full_text) for_each_buffer_client(bufnr, function(client, _client_id) if client.resolved_capabilities.text_document_save then local included_text @@ -916,7 +934,7 @@ function lsp.buf_attach_client(bufnr, client_id) on_lines = text_document_did_change_handler; on_detach = function() local params = { textDocument = { uri = uri; } } - for_each_buffer_client(bufnr, function(client, _client_id) + for_each_buffer_client(bufnr, function(client, _) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end @@ -930,6 +948,13 @@ function lsp.buf_attach_client(bufnr, client_id) utf_sizes = true; }) end + + if not all_client_active_buffers[client_id] then + all_client_active_buffers[client_id] = {} + end + + table.insert(all_client_active_buffers[client_id], bufnr) + if buffer_client_ids[client_id] then return end -- This is our first time attaching this client to this buffer. buffer_client_ids[client_id] = true @@ -961,6 +986,19 @@ function lsp.get_client_by_id(client_id) return active_clients[client_id] or uninitialized_clients[client_id] end +--- Returns list of buffers attached to client_id. +-- +--@param client_id client id +--@returns list of buffer ids +function lsp.get_buffers_by_client_id(client_id) + local active_client_buffers = all_client_active_buffers[client_id] + if active_client_buffers then + return active_client_buffers + else + return {} + end +end + --- Stops a client(s). --- --- You can also use the `stop()` function on a |vim.lsp.client| object. @@ -978,12 +1016,23 @@ end function lsp.stop_client(client_id, force) local ids = type(client_id) == 'table' and client_id or {client_id} for _, id in ipairs(ids) do + local resolved_client_id if type(id) == 'table' and id.stop ~= nil then id.stop(force) + resolved_client_id = id.id elseif active_clients[id] then active_clients[id].stop(force) + resolved_client_id = id elseif uninitialized_clients[id] then uninitialized_clients[id].stop(true) + resolved_client_id = id + end + if resolved_client_id then + local client_buffers = lsp.get_buffers_by_client_id(resolved_client_id) + for idx = 1, #client_buffers do + lsp.diagnostic.clear(client_buffers[idx], resolved_client_id) + end + all_client_active_buffers[resolved_client_id] = nil end end end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 072349b226..a625098bab 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -16,6 +16,24 @@ local to_severity = function(severity) return type(severity) == 'string' and DiagnosticSeverity[severity] or severity end +local filter_to_severity_limit = function(severity, diagnostics) + local filter_level = to_severity(severity) + if not filter_level then + return diagnostics + end + + return vim.tbl_filter(function(t) return t.severity == filter_level end, diagnostics) +end + +local filter_by_severity_limit = function(severity_limit, diagnostics) + local filter_level = to_severity(severity_limit) + if not filter_level then + return diagnostics + end + + return vim.tbl_filter(function(t) return t.severity <= filter_level end, diagnostics) +end + local to_position = function(position, bufnr) vim.validate { position = {position, 't'} } @@ -377,11 +395,9 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) end if opts.severity then - local filter_level = to_severity(opts.severity) - line_diagnostics = vim.tbl_filter(function(t) return t.severity == filter_level end, line_diagnostics) + line_diagnostics = filter_to_severity_limit(opts.severity, line_diagnostics) elseif opts.severity_limit then - local filter_level = to_severity(opts.severity_limit) - line_diagnostics = vim.tbl_filter(function(t) return t.severity <= filter_level end, line_diagnostics) + line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) end if opts.severity_sort then @@ -542,7 +558,7 @@ function M.goto_prev(opts) ) end ---- Get the previous diagnostic closest to the cursor_position +--- Get the next diagnostic closest to the cursor_position ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic function M.get_next(opts) @@ -609,6 +625,8 @@ end ---@param sign_ns number|nil ---@param opts table Configuration for signs. Keys: --- - priority: Set the priority of the signs. +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) opts = opts or {} sign_ns = sign_ns or M._get_sign_namespace(client_id) @@ -622,9 +640,11 @@ function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) end bufnr = get_bufnr(bufnr) + diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) local ok = true for _, diagnostic in ipairs(diagnostics) do + ok = ok and pcall(vim.fn.sign_place, 0, sign_ns, @@ -654,15 +674,17 @@ end --- </pre> --- ---@param diagnostics Diagnostic[] ----@param bufnr number The buffer number ----@param client_id number the client id ----@param diagnostic_ns number|nil ----@param opts table Currently unused. +---@param bufnr number: The buffer number +---@param client_id number: The client id +---@param diagnostic_ns number|nil: The namespace +---@param opts table: Configuration table: +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_underline(diagnostics, bufnr, client_id, diagnostic_ns, opts) opts = opts or {} - assert(opts) -- lint diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) + diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -703,6 +725,8 @@ end ---@param opts table Options on how to display virtual text. Keys: --- - prefix (string): Prefix to display before virtual text on line --- - spacing (number): Number of spaces to insert before virtual text +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) opts = opts or {} @@ -721,6 +745,7 @@ function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) end for line, line_diagnostics in pairs(buffer_line_diagnostics) do + line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) local virt_texts = M.get_virtual_text_chunks_for_line(bufnr, line, line_diagnostics, opts) if virt_texts then @@ -1082,7 +1107,7 @@ end ---@param bufnr number The buffer number ---@param line_nr number The line number ---@param client_id number|nil the client id ----@return {popup_bufnr, win_id} +---@return table {popup_bufnr, win_id} function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) opts = opts or {} opts.severity_sort = if_nil(opts.severity_sort, true) @@ -1151,30 +1176,14 @@ function M.set_loclist(opts) local bufnr = vim.api.nvim_get_current_buf() local buffer_diags = M.get(bufnr, opts.client_id) - local severity = to_severity(opts.severity) - local severity_limit = to_severity(opts.severity_limit) + if opts.severity then + buffer_diags = filter_to_severity_limit(opts.severity, buffer_diags) + elseif opts.severity_limit then + buffer_diags = filter_by_severity_limit(opts.severity_limit, buffer_diags) + end local items = {} local insert_diag = function(diag) - if severity then - -- Handle missing severities - if not diag.severity then - return - end - - if severity ~= diag.severity then - return - end - elseif severity_limit then - if not diag.severity then - return - end - - if severity_limit < diag.severity then - return - end - end - local pos = diag.range.start local row = pos.line local col = util.character_offset(bufnr, row, pos.character) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 87f35363b1..7eac3febd9 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -97,6 +97,18 @@ M['window/showMessageRequest'] = function(_, _, params) end end +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability +M['client/registerCapability'] = function(_, _, _, client_id) + local warning_tpl = "The language server %s triggers a registerCapability ".. + "handler despite dynamicRegistration set to false. ".. + "Report upstream, this warning is harmless" + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + local warning = string.format(warning_tpl, client_name) + log.warn(warning) + return vim.NIL +end + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction M['textDocument/codeAction'] = function(_, _, actions) if actions == nil or vim.tbl_isempty(actions) then @@ -150,6 +162,7 @@ M['workspace/configuration'] = function(err, _, params, client_id) local client = vim.lsp.get_client_by_id(client_id) if not client then err_message("LSP[id=", client_id, "] client has shut down after sending the message") + return end if err then error(vim.inspect(err)) end if not params.items then diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 587a65cd96..b6e91e37b9 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -28,7 +28,7 @@ do local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) end - local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log') + local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log') --- Returns the log filename. --@returns (string) log filename @@ -36,7 +36,7 @@ do return logfilename end - vim.fn.mkdir(vim.fn.stdpath('data'), "p") + vim.fn.mkdir(vim.fn.stdpath('cache'), "p") local logfile = assert(io.open(logfilename, "a+")) for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index b2d3d0641c..3e111c154a 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -34,6 +34,13 @@ local constants = { Hint = 4; }; + DiagnosticTag = { + -- Unused or unnecessary code + Unnecessary = 1; + -- Deprecated or obsolete code + Deprecated = 2; + }; + MessageType = { -- An error message. Error = 1; @@ -521,6 +528,13 @@ export interface TextDocumentClientCapabilities { publishDiagnostics?: { --Whether the clients accepts diagnostics with related information. relatedInformation?: boolean; + --Client supports the tag property to provide meta data about a diagnostic. + --Clients supporting tags have to handle unknown tags gracefully. + --Since 3.15.0 + tagSupport?: { + --The tags supported by this client + valueSet: DiagnosticTag[]; + }; }; --Capabilities specific to `textDocument/foldingRange` requests. -- @@ -706,6 +720,18 @@ function protocol.make_client_capabilities() dynamicRegistration = false; prepareSupport = true; }; + publishDiagnostics = { + relatedInformation = true; + tagSupport = { + valueSet = (function() + local res = {} + for k in ipairs(protocol.DiagnosticTag) do + if type(k) == 'number' then table.insert(res, k) end + end + return res + end)(); + }; + }; }; workspace = { symbol = { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ecff95f61e..00bdeecef3 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -254,19 +254,27 @@ function M.extract_completion_items(result) end --- Applies a `TextDocumentEdit`, which is a list of changes to a single --- document. +--- document. --- ---@param text_document_edit (table) a `TextDocumentEdit` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit) +---@param text_document_edit table: a `TextDocumentEdit` object +---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) +---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit +function M.apply_text_document_edit(text_document_edit, index) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + -- For lists of text document edits, + -- do not check the version after the first edit. + local should_check_version = true + if index and index > 1 then + should_check_version = false + end + -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier - if text_document.version + if should_check_version and (text_document.version and M.buf_versions[bufnr] - and M.buf_versions[bufnr] > text_document.version then + and M.buf_versions[bufnr] > text_document.version) then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -459,12 +467,12 @@ end -- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then - for _, change in ipairs(workspace_edit.documentChanges) do + for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind then -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change) + M.apply_text_document_edit(change, idx) end end return @@ -718,7 +726,7 @@ function M.focusable_float(unique_name, fn) local bufnr = api.nvim_get_current_buf() do local win = find_window_by_var(unique_name, bufnr) - if win and api.nvim_win_is_valid(win) and not vim.fn.pumvisible() then + if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then api.nvim_set_current_win(win) api.nvim_command("stopinsert") return diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 79dcf77f9e..38ac182e32 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -50,7 +50,7 @@ function M._create_parser(bufnr, lang, opts) end end - a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true}) + a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true}) self:parse() diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 9c620c422c..c864fe5878 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -425,23 +425,21 @@ function LanguageTree:register_cbs(cbs) end end -local function region_contains(region, range) - for _, node in ipairs(region) do - local start_row, start_col, end_row, end_col = node:range() - local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) - local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) +local function tree_contains(tree, range) + local start_row, start_col, end_row, end_col = tree:root():range() + local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) + local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) - if start_fits and end_fits then - return true - end + if start_fits and end_fits then + return true end return false end function LanguageTree:contains(range) - for _, region in pairs(self._regions) do - if region_contains(region, range) then + for _, tree in pairs(self._trees) do + if tree_contains(tree, range) then return true end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5a27d740a2..e49f54681d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -340,6 +340,19 @@ function Query:apply_directives(match, pattern, source, metadata) end end + +--- Returns the start and stop value if set else the node's range. +-- When the node's range is used, the stop is incremented by 1 +-- to make the search inclusive. +local function value_or_node_range(start, stop, node) + if start == nil and stop == nil then + local node_start, _, node_stop, _ = node:range() + return node_start, node_stop + 1 -- Make stop inclusive + end + + return start, stop +end + --- Iterates of the captures of self on a given range. -- -- @param node The node under witch the search will occur @@ -353,6 +366,9 @@ function Query:iter_captures(node, source, start, stop) if type(source) == "number" and source == 0 then source = vim.api.nvim_get_current_buf() end + + start, stop = value_or_node_range(start, stop, node) + local raw_iter = node:_rawquery(self.query, true, start, stop) local function iter() local capture, captured_node, match = raw_iter() @@ -385,6 +401,9 @@ function Query:iter_matches(node, source, start, stop) if type(source) == "number" and source == 0 then source = vim.api.nvim_get_current_buf() end + + start, stop = value_or_node_range(start, stop, node) + local raw_iter = node:_rawquery(self.query, false, start, stop) local function iter() local pattern, match = raw_iter() diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index 025de1b5a9..e99c76a930 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,6 +26,7 @@ </screenshots> <releases> + <release date="2020-08-04" version="0.4.4"/> <release date="2019-11-06" version="0.4.3"/> <release date="2019-09-15" version="0.4.2"/> <release date="2019-09-15" version="0.4.1"/> diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 6aae2b1ec3..3b3409bf4b 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -182,6 +182,14 @@ if s:line1 =~# "^#!" elseif s:name =~# 'clojure' set ft=clojure + " Free Pascal + elseif s:name =~# 'instantfpc\>' + set ft=pascal + + " Fennel + elseif s:name =~# 'fennel\>' + set ft=fennel + endif unlet s:name diff --git a/runtime/syntax/elm.vim b/runtime/syntax/elm.vim new file mode 100644 index 0000000000..1277827f57 --- /dev/null +++ b/runtime/syntax/elm.vim @@ -0,0 +1,105 @@ +" Vim syntax file +" Language: Elm +" Maintainer: Andreas Scharf <as@99n.de> +" Original Author: Joseph Hager <ajhager@gmail.com> +" Copyright: Joseph Hager <ajhager@gmail.com> +" License: BSD3 +" Latest Revision: 2020-05-29 + +if exists('b:current_syntax') + finish +endif + +" Keywords +syn keyword elmConditional else if of then case +syn keyword elmAlias alias +syn keyword elmTypedef contained type port +syn keyword elmImport exposing as import module where + +" Operators +" elm/core +syn match elmOperator contained "\(<|\||>\|||\|&&\|==\|/=\|<=\|>=\|++\|::\|+\|-\|*\|/\|//\|^\|<>\|>>\|<<\|<\|>\|%\)" +" elm/parser +syn match elmOperator contained "\(|.\||=\)" +" elm/url +syn match elmOperator contained "\(</>\|<?>\)" + +" Types +syn match elmType "\<[A-Z][0-9A-Za-z_-]*" +syn keyword elmNumberType number + +" Modules +syn match elmModule "\<\([A-Z][0-9A-Za-z_'-\.]*\)\+\.[A-Za-z]"me=e-2 +syn match elmModule "^\(module\|import\)\s\+[A-Z][0-9A-Za-z_'-\.]*\(\s\+as\s\+[A-Z][0-9A-Za-z_'-\.]*\)\?\(\s\+exposing\)\?" contains=elmImport + +" Delimiters +syn match elmDelimiter "[,;]" +syn match elmBraces "[()[\]{}]" + +" Functions +syn match elmTupleFunction "\((,\+)\)" + +" Comments +syn keyword elmTodo TODO FIXME XXX contained +syn match elmLineComment "--.*" contains=elmTodo,@spell +syn region elmComment matchgroup=elmComment start="{-|\=" end="-}" contains=elmTodo,elmComment,@spell fold + +" Strings +syn match elmStringEscape "\\u[0-9a-fA-F]\{4}" contained +syn match elmStringEscape "\\[nrfvbt\\\"]" contained +syn region elmString start="\"" skip="\\\"" end="\"" contains=elmStringEscape,@spell +syn region elmTripleString start="\"\"\"" skip="\\\"" end="\"\"\"" contains=elmStringEscape,@spell +syn match elmChar "'[^'\\]'\|'\\.'\|'\\u[0-9a-fA-F]\{4}'" + +" Lambda +syn region elmLambdaFunc start="\\"hs=s+1 end="->"he=e-2 + +" Debug +syn match elmDebug "Debug.\(log\|todo\|toString\)" + +" Numbers +syn match elmInt "-\?\<\d\+\>" +syn match elmFloat "-\?\(\<\d\+\.\d\+\>\)" + +" Identifiers +syn match elmTopLevelDecl "^\s*[a-zA-Z][a-zA-z0-9_]*\('\)*\s\+:\(\r\n\|\r\|\n\|\s\)\+" contains=elmOperator +syn match elmFuncName /^\l\w*/ + +" Folding +syn region elmTopLevelTypedef start="type" end="\n\(\n\n\)\@=" contains=ALL fold +syn region elmTopLevelFunction start="^[a-zA-Z].\+\n[a-zA-Z].\+=" end="^\(\n\+\)\@=" contains=ALL fold +syn region elmCaseBlock matchgroup=elmCaseBlockDefinition start="^\z\(\s\+\)\<case\>" end="^\z1\@!\W\@=" end="\(\n\n\z1\@!\)\@=" end="\n\z1\@!\(\n\n\)\@=" contains=ALL fold +syn region elmCaseItemBlock start="^\z\(\s\+\).\+->$" end="^\z1\@!\W\@=" end="\(\n\n\z1\@!\)\@=" end="\(\n\z1\S\)\@=" contains=ALL fold +syn region elmLetBlock matchgroup=elmLetBlockDefinition start="\<let\>" end="\<in\>" contains=ALL fold + +hi def link elmFuncName Function +hi def link elmCaseBlockDefinition Conditional +hi def link elmCaseBlockItemDefinition Conditional +hi def link elmLetBlockDefinition TypeDef +hi def link elmTopLevelDecl Function +hi def link elmTupleFunction Normal +hi def link elmTodo Todo +hi def link elmComment Comment +hi def link elmLineComment Comment +hi def link elmString String +hi def link elmTripleString String +hi def link elmChar String +hi def link elmStringEscape Special +hi def link elmInt Number +hi def link elmFloat Float +hi def link elmDelimiter Delimiter +hi def link elmBraces Delimiter +hi def link elmTypedef TypeDef +hi def link elmImport Include +hi def link elmConditional Conditional +hi def link elmAlias Delimiter +hi def link elmOperator Operator +hi def link elmType Type +hi def link elmNumberType Identifier +hi def link elmLambdaFunc Function +hi def link elmDebug Debug +hi def link elmModule Type + +syn sync minlines=500 + +let b:current_syntax = 'elm' diff --git a/runtime/syntax/zsh.vim b/runtime/syntax/zsh.vim index 3eba438aa7..819c419228 100644 --- a/runtime/syntax/zsh.vim +++ b/runtime/syntax/zsh.vim @@ -2,7 +2,7 @@ " Language: Zsh shell script " Maintainer: Christian Brabandt <cb@256bit.org> " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2018-05-12 +" Latest Revision: 2020-11-21 " License: Vim (see :h license) " Repository: https://github.com/chrisbra/vim-zsh @@ -13,32 +13,44 @@ endif let s:cpo_save = &cpo set cpo&vim -if v:version > 704 || (v:version == 704 && has("patch1142")) - syn iskeyword @,48-57,_,192-255,#,- -else - setlocal iskeyword+=- -endif +function! s:ContainedGroup() + " needs 7.4.2008 for execute() function + let result='TOP' + " vim-pandoc syntax defines the @langname cluster for embedded syntax languages + " However, if no syntax is defined yet, `syn list @zsh` will return + " "No syntax items defined", so make sure the result is actually a valid syn cluster + for cluster in ['markdownHighlightzsh', 'zsh'] + try + " markdown syntax defines embedded clusters as @markdownhighlight<lang>, + " pandoc just uses @<lang>, so check both for both clusters + let a=split(execute('syn list @'. cluster), "\n") + if len(a) == 2 && a[0] =~# '^---' && a[1] =~? cluster + return '@'. cluster + endif + catch /E392/ + " ignore + endtry + endfor + return result +endfunction + +let s:contained=s:ContainedGroup() + +syn iskeyword @,48-57,_,192-255,#,- if get(g:, 'zsh_fold_enable', 0) setlocal foldmethod=syntax endif -syn keyword zshTodo contained TODO FIXME XXX NOTE - -syn region zshComment oneline start='\%(^\|\s\+\)#' end='$' - \ contains=zshTodo,@Spell fold - -syn region zshComment start='^\s*#' end='^\%(\s*#\)\@!' - \ contains=zshTodo,@Spell fold - -syn match zshPreProc '^\%1l#\%(!\|compdef\|autoload\).*$' - +syn match zshPOSIXQuoted '\\[xX][0-9a-fA-F]\{1,2}' +syn match zshPOSIXQuoted '\\[0-7]\{1,3}' +syn match zshPOSIXQuoted '\\u[0-9a-fA-F]\{1,4}' +syn match zshPOSIXQuoted '\\U[1-9a-fA-F]\{1,8}' syn match zshQuoted '\\.' syn region zshString matchgroup=zshStringDelimiter start=+"+ end=+"+ \ contains=zshQuoted,@zshDerefs,@zshSubst fold syn region zshString matchgroup=zshStringDelimiter start=+'+ end=+'+ fold -" XXX: This should probably be more precise, but Zsh seems a bit confused about it itself syn region zshPOSIXString matchgroup=zshStringDelimiter start=+\$'+ - \ end=+'+ contains=zshQuoted + \ skip=+\\[\\']+ end=+'+ contains=zshPOSIXQuoted,zshQuoted syn match zshJobSpec '%\(\d\+\|?\=\w\+\|[%+-]\)' syn keyword zshPrecommand noglob nocorrect exec command builtin - time @@ -112,7 +124,7 @@ syn keyword zshCommands alias autoload bg bindkey break bye cap cd \ enable eval exec exit export false fc fg \ functions getcap getln getopts hash history \ jobs kill let limit log logout popd print - \ printf pushd pushln pwd r read readonly + \ printf pushd pushln pwd r read \ rehash return sched set setcap shift \ source stat suspend test times trap true \ ttyctl type ulimit umask unalias unfunction @@ -125,7 +137,7 @@ syn keyword zshCommands alias autoload bg bindkey break bye cap cd " Create a list of option names from zsh source dir: " #!/bin/zsh " topdir=/path/to/zsh-xxx -" grep '^pindex([A-Za-z_]*)$' $topdir/Src/Doc/Zsh/optionsyo | +" grep '^pindex([A-Za-z_]*)$' $topdir/Doc/Zsh/options.yo | " while read opt " do " echo ${${(L)opt#pindex\(}%\)} @@ -136,6 +148,7 @@ syn case ignore syn match zshOptStart /^\s*\%(\%(\%(un\)\?setopt\)\|set\s+[-+]o\)/ nextgroup=zshOption skipwhite syn match zshOption / \ \%(\%(\<no_\?\)\?aliases\>\)\| + \ \%(\%(\<no_\?\)\?aliasfuncdef\>\)\|\%(\%(no_\?\)\?alias_func_def\>\)\| \ \%(\%(\<no_\?\)\?allexport\>\)\|\%(\%(no_\?\)\?all_export\>\)\| \ \%(\%(\<no_\?\)\?alwayslastprompt\>\)\|\%(\%(no_\?\)\?always_last_prompt\>\)\|\%(\%(no_\?\)\?always_lastprompt\>\)\| \ \%(\%(\<no_\?\)\?alwaystoend\>\)\|\%(\%(no_\?\)\?always_to_end\>\)\| @@ -165,10 +178,13 @@ syn match zshOption / \ \%(\%(\<no_\?\)\?casematch\>\)\|\%(\%(no_\?\)\?case_match\>\)\| \ \%(\%(\<no_\?\)\?cbases\>\)\|\%(\%(no_\?\)\?c_bases\>\)\| \ \%(\%(\<no_\?\)\?cdablevars\>\)\|\%(\%(no_\?\)\?cdable_vars\>\)\|\%(\%(no_\?\)\?cd_able_vars\>\)\| + \ \%(\%(\<no_\?\)\?cdsilent\>\)\|\%(\%(no_\?\)\?cd_silent\>\)\|\%(\%(no_\?\)\?cd_silent\>\)\| \ \%(\%(\<no_\?\)\?chasedots\>\)\|\%(\%(no_\?\)\?chase_dots\>\)\| \ \%(\%(\<no_\?\)\?chaselinks\>\)\|\%(\%(no_\?\)\?chase_links\>\)\| \ \%(\%(\<no_\?\)\?checkjobs\>\)\|\%(\%(no_\?\)\?check_jobs\>\)\| + \ \%(\%(\<no_\?\)\?checkrunningjobs\>\)\|\%(\%(no_\?\)\?check_running_jobs\>\)\| \ \%(\%(\<no_\?\)\?clobber\>\)\| + \ \%(\%(\<no_\?\)\?clobberempty\>\)\|\%(\%(no_\?\)\?clobber_empty\>\)\| \ \%(\%(\<no_\?\)\?combiningchars\>\)\|\%(\%(no_\?\)\?combining_chars\>\)\| \ \%(\%(\<no_\?\)\?completealiases\>\)\|\%(\%(no_\?\)\?complete_aliases\>\)\| \ \%(\%(\<no_\?\)\?completeinword\>\)\|\%(\%(no_\?\)\?complete_in_word\>\)\| @@ -188,7 +204,7 @@ syn match zshOption / \ \%(\%(\<no_\?\)\?equals\>\)\| \ \%(\%(\<no_\?\)\?errexit\>\)\|\%(\%(no_\?\)\?err_exit\>\)\| \ \%(\%(\<no_\?\)\?errreturn\>\)\|\%(\%(no_\?\)\?err_return\>\)\| - \ \%(\%(\<no_\?\)\?evallineno_\?\)\|\%(\%(no_\?\)\?eval_lineno_\?\)\| + \ \%(\%(\<no_\?\)\?evallineno\>\)\|\%(\%(no_\?\)\?eval_lineno\>\)\| \ \%(\%(\<no_\?\)\?exec\>\)\| \ \%(\%(\<no_\?\)\?extendedglob\>\)\|\%(\%(no_\?\)\?extended_glob\>\)\| \ \%(\%(\<no_\?\)\?extendedhistory\>\)\|\%(\%(no_\?\)\?extended_history\>\)\| @@ -309,6 +325,7 @@ syn match zshOption / \ \%(\%(\<no_\?\)\?shnullcmd\>\)\|\%(\%(no_\?\)\?sh_nullcmd\>\)\| \ \%(\%(\<no_\?\)\?shoptionletters\>\)\|\%(\%(no_\?\)\?sh_option_letters\>\)\| \ \%(\%(\<no_\?\)\?shortloops\>\)\|\%(\%(no_\?\)\?short_loops\>\)\| + \ \%(\%(\<no_\?\)\?shortrepeat\>\)\|\%(\%(no_\?\)\?short_repeat\>\)\| \ \%(\%(\<no_\?\)\?shwordsplit\>\)\|\%(\%(no_\?\)\?sh_word_split\>\)\| \ \%(\%(\<no_\?\)\?singlecommand\>\)\|\%(\%(no_\?\)\?single_command\>\)\| \ \%(\%(\<no_\?\)\?singlelinezle\>\)\|\%(\%(no_\?\)\?single_line_zle\>\)\| @@ -322,10 +339,13 @@ syn match zshOption / \ \%(\%(\<no_\?\)\?unset\>\)\| \ \%(\%(\<no_\?\)\?verbose\>\)\| \ \%(\%(\<no_\?\)\?vi\>\)\| + \ \%(\%(\<no_\?\)\?warnnestedvar\>\)\|\%(\%(no_\?\)\?warn_nested_var\>\)\| \ \%(\%(\<no_\?\)\?warncreateglobal\>\)\|\%(\%(no_\?\)\?warn_create_global\>\)\| \ \%(\%(\<no_\?\)\?xtrace\>\)\| \ \%(\%(\<no_\?\)\?zle\>\)/ nextgroup=zshOption,zshComment skipwhite contained +syn case match + syn keyword zshTypes float integer local typeset declare private readonly " XXX: this may be too much @@ -339,31 +359,42 @@ syn match zshNumber '[+-]\=\d\+\.\d\+\>' " TODO: $[...] is the same as $((...)), so add that as well. syn cluster zshSubst contains=zshSubst,zshOldSubst,zshMathSubst -syn region zshSubst matchgroup=zshSubstDelim transparent - \ start='\$(' skip='\\)' end=')' contains=TOP fold +exe 'syn region zshSubst matchgroup=zshSubstDelim transparent start=/\$(/ skip=/\\)/ end=/)/ contains='.s:contained. ' fold' syn region zshParentheses transparent start='(' skip='\\)' end=')' fold syn region zshGlob start='(#' end=')' syn region zshMathSubst matchgroup=zshSubstDelim transparent - \ start='\$((' skip='\\)' end='))' + \ start='\%(\$\?\)[<=>]\@<!((' skip='\\)' end='))' \ contains=zshParentheses,@zshSubst,zshNumber, \ @zshDerefs,zshString keepend fold -syn region zshBrackets contained transparent start='{' skip='\\}' +" The ms=s+1 prevents matching zshBrackets several times on opening brackets +" (see https://github.com/chrisbra/vim-zsh/issues/21#issuecomment-576330348) +syn region zshBrackets contained transparent start='{'ms=s+1 skip='\\}' \ end='}' fold -syn region zshBrackets transparent start='{' skip='\\}' - \ end='}' contains=TOP fold +exe 'syn region zshBrackets transparent start=/{/ms=s+1 skip=/\\}/ end=/}/ contains='.s:contained. ' fold' + syn region zshSubst matchgroup=zshSubstDelim start='\${' skip='\\}' \ end='}' contains=@zshSubst,zshBrackets,zshQuoted,zshString fold -syn region zshOldSubst matchgroup=zshSubstDelim start=+`+ skip=+\\`+ - \ end=+`+ contains=TOP,zshOldSubst fold +exe 'syn region zshOldSubst matchgroup=zshSubstDelim start=/`/ skip=/\\[\\`]/ end=/`/ contains='.s:contained. ',zshOldSubst fold' syn sync minlines=50 maxlines=90 syn sync match zshHereDocSync grouphere NONE '<<-\=\s*\%(\\\=\S\+\|\(["']\)\S\+\1\)' syn sync match zshHereDocEndSync groupthere NONE '^\s*EO\a\+\>' +syn keyword zshTodo contained TODO FIXME XXX NOTE + +syn region zshComment oneline start='\%(^\|\s\+\)#' end='$' + \ contains=zshTodo,@Spell fold + +syn region zshComment start='^\s*#' end='^\%(\s*#\)\@!' + \ contains=zshTodo,@Spell fold + +syn match zshPreProc '^\%1l#\%(!\|compdef\|autoload\).*$' + hi def link zshTodo Todo hi def link zshComment Comment hi def link zshPreProc PreProc hi def link zshQuoted SpecialChar +hi def link zshPOSIXQuoted SpecialChar hi def link zshString String hi def link zshStringDelimiter zshString hi def link zshPOSIXString zshString diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 551b8fb691..f583b2fdea 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -180,8 +180,12 @@ preprocess_patch() { local file="$1" local nvim="nvim -u NORC -i NONE --headless" - # Remove *.proto, Make*, gui_*, some if_* - local na_src='proto\|Make*\|gui_*\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv' + # Remove Filelist, README + local na_files='Filelist\|README.*' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/\<\%('"${na_files}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + + # Remove *.proto, Make*, INSTALL*, gui_*, beval.*, some if_*, gvim, libvterm, tee, VisVim, xpm, xxd + local na_src='auto\|configure.*\|GvimExt\|libvterm\|proto\|tee\|VisVim\|xpm\|xxd\|Make*\|INSTALL*\|beval.*\|gui_*\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove unwanted Vim doc files. @@ -191,10 +195,14 @@ preprocess_patch() { # Remove "Last change ..." changes in doc files. 2>/dev/null $nvim --cmd 'set dir=/tmp' +'%s/^@@.*\n.*For Vim version.*Last change.*\n.*For Vim version.*Last change.*//' +w +q "$file" - # Remove screen dumps, testdir/Make_*.mak files - local na_src_testdir='Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms\|dumps/.*.dump' + # Remove gui, option, setup, screen dumps, testdir/Make_*.mak files + local na_src_testdir='gen_opt_test.vim\|gui_.*\|Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms\|dumps/.*.dump\|setup_gui.vim' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + # Remove testdir/test_*.vim files + local na_src_testdir='balloon.*\|channel.*\|crypt.vim\|gui.*\|job_fails.vim\|json.vim\|mzscheme.vim\|netbeans.*\|paste.vim\|popupwin.*\|restricted.vim\|shortpathname.vim\|tcl.vim\|terminal.*\|xxd.vim' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<test_\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + # Remove version.c #7555 local na_po='version.c' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" diff --git a/src/nvim/README.md b/src/nvim/README.md index d14ba85546..affc5c79cc 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -38,7 +38,7 @@ alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to filter the log, e.g. at DEBUG level you might want to exclude UI messages: - tail -F ~/.local/share/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log + tail -F ~/.cache/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log Build with ASAN --------------- diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8d82d22040..67f4f92bf6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1200,8 +1200,7 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) /// @param ns_id Namespace id from |nvim_create_namespace()| /// @param id Extmark id /// @param opts Optional parameters. Keys: -/// - limit: Maximum number of marks to return -/// - details Whether to include the details dict +/// - details: Whether to include the details dict /// @param[out] err Error details, if any /// @return (row, col) tuple or empty list () if extmark id was absent ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, @@ -1400,6 +1399,13 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the /// buffer. +/// - right_gravity : boolean that indicates the direction +/// the extmark will be shifted in when new text is inserted +/// (true for right, false for left). defaults to true. +/// - end_right_gravity : boolean that indicates the direction +/// the extmark end position (if it exists) will be shifted +/// in when new text is inserted (true for right, false +/// for left). Defaults to false. /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, @@ -1440,6 +1446,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, DecorPriority priority = DECOR_PRIORITY_BASE; colnr_T col2 = 0; VirtText virt_text = KV_INITIAL_VALUE; + bool right_gravity = true; + bool end_right_gravity = false; + bool end_gravity_set = false; + for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; @@ -1522,12 +1532,35 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, goto error; } priority = (DecorPriority)v->data.integer; + } else if (strequal("right_gravity", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, + "right_gravity must be a boolean"); + goto error; + } + right_gravity = v->data.boolean; + } else if (strequal("end_right_gravity", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, + "end_right_gravity must be a boolean"); + goto error; + } + end_right_gravity = v->data.boolean; + end_gravity_set = true; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; } } + // Only error out if they try to set end_right_gravity without + // setting end_col or end_line + if (line2 == -1 && col2 == 0 && end_gravity_set) { + api_set_error(err, kErrorTypeValidation, + "cannot set end_right_gravity " + "without setting end_line or end_col"); + } + if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); @@ -1572,7 +1605,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, - line2, col2, decor, kExtmarkNoUndo); + line2, col2, decor, right_gravity, + end_right_gravity, kExtmarkNoUndo); } return (Integer)id; @@ -1687,7 +1721,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, extmark_set(buf, ns_id, 0, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor_hl(hl_id), kExtmarkNoUndo); + decor_hl(hl_id), true, false, kExtmarkNoUndo); return src_id; } @@ -1796,7 +1830,8 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Decoration *decor = xcalloc(1, sizeof(*decor)); decor->virt_text = virt_text; - extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkNoUndo); + extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, true, + false, kExtmarkNoUndo); return src_id; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8f224e8c78..7cee569989 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,43 @@ bool api_object_to_bool(Object obj, const char *what, } } +HlMessage parse_hl_msg(Array chunks, Error *err) +{ + HlMessage hl_msg = KV_INITIAL_VALUE; + for (size_t i = 0; i < chunks.size; i++) { + if (chunks.items[i].type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); + goto free_exit; + } + Array chunk = chunks.items[i].data.array; + if (chunk.size == 0 || chunk.size > 2 + || chunk.items[0].type != kObjectTypeString + || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + api_set_error(err, kErrorTypeValidation, + "Chunk is not an array with one or two strings"); + goto free_exit; + } + + String str = copy_string(chunk.items[0].data.string); + + int attr = 0; + if (chunk.size == 2) { + String hl = chunk.items[1].data.string; + if (hl.size > 0) { + int hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; + } + } + kv_push(hl_msg, ((HlMessageChunk){ .text = str, .attr = attr })); + } + + return hl_msg; + +free_exit: + clear_hl_msg(&hl_msg); + return hl_msg; +} + const char *describe_ns(NS ns_id) { String name; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1e972e01be..09895a2119 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -990,6 +990,48 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err); } +/// Echo a message. +/// +/// @param chunks A list of [text, hl_group] arrays, each representing a +/// text chunk with specified highlight. `hl_group` element +/// can be omitted for no highlight. +/// @param history if true, add to |message-history|. +/// @param opts Optional parameters. Reserved for future use. +void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + HlMessage hl_msg = parse_hl_msg(chunks, err); + if (ERROR_SET(err)) { + goto error; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + goto error; + } + + no_wait_return++; + msg_start(); + msg_clr_eos(); + bool need_clear = false; + for (uint32_t i = 0; i < kv_size(hl_msg); i++) { + HlMessageChunk chunk = kv_A(hl_msg, i); + msg_multiline_attr((const char *)chunk.text.data, chunk.attr, + false, &need_clear); + } + if (history) { + msg_ext_set_kind("echomsg"); + add_hl_msg_hist(hl_msg); + } else { + msg_ext_set_kind("echo"); + } + no_wait_return--; + msg_end(); + +error: + clear_hl_msg(&hl_msg); +} + /// Writes a message to the Vim output buffer. Does not append "\n", the /// message is buffered (won't display) until a linefeed is written. /// diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 42224d0a4f..140a9c6bcb 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -700,11 +700,15 @@ void do_autocmd(char_u *arg_in, int forceit) last_event = (event_T)-1; // for listing the event name last_group = AUGROUP_ERROR; // for listing the group name if (*arg == '*' || *arg == NUL || *arg == '|') { - for (event_T event = (event_T)0; event < (int)NUM_EVENTS; - event = (event_T)(event + 1)) { - if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) - == FAIL) { - break; + if (!forceit && *cmd != NUL) { + EMSG(_(e_cannot_define_autocommands_for_all_events)); + } else { + for (event_T event = (event_T)0; event < (int)NUM_EVENTS; + event = (event_T)(event + 1)) { + if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) + == FAIL) { + break; + } } } } else { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 76205c70ae..7116b25b29 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3270,7 +3270,7 @@ void maketitle(void) case 6: buf_p = strappend(buf_p, " -"); break; case 5: case 7: buf_p = strappend(buf_p, " -+"); break; - default: assert(false); + default: abort(); } if (curbuf->b_fname != NULL) { diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 37cbfb968b..5628ede2e6 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -304,7 +304,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, bool pty, bool rpc, bool overlapped, bool detach, const char *cwd, uint16_t pty_width, uint16_t pty_height, - char *term_name, char **env, varnumber_T *status_out) + char *term_name, dict_T *env, + varnumber_T *status_out) { assert(cwd == NULL || os_isdir_executable(cwd)); @@ -358,7 +359,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); - os_free_fullenv(proc->env); + if (proc->env) { + tv_dict_free(proc->env); + } if (proc->type == kProcessTypePty) { xfree(chan->stream.pty.term_name); } @@ -367,8 +370,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, return NULL; } xfree(cmd); - os_free_fullenv(proc->env); - + if (proc->env) { + tv_dict_free(proc->env); + } wstream_init(&proc->in, 0); if (has_out) { diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 3e52b3e3ce..be265e3f27 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1747,7 +1747,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, goto vim_str2nr_dec; } default: { - assert(false); + abort(); } } } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) @@ -1788,7 +1788,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - assert(false); // Should’ve used goto earlier. + abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e6a616c927..f3ee42fab1 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -69,7 +69,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, } (void)extmark_set(buf, (uint64_t)src_id, 0, (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, - decor, kExtmarkNoUndo); + decor, true, false, kExtmarkNoUndo); } } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 876e53e3cd..100e88e261 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -313,6 +313,11 @@ static void insert_enter(InsertState *s) set_vim_var_string(VV_CHAR, NULL, -1); ins_apply_autocmds(EVENT_INSERTENTER); + // Check for changed highlighting, e.g. for ModeMsg. + if (need_highlight_changed) { + highlight_changed(); + } + // Make sure the cursor didn't move. Do call check_cursor_col() in // case the text was modified. Since Insert mode was not started yet // a call to check_cursor_col() may move the cursor, especially with @@ -8282,8 +8287,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } } while (revins_on || (curwin->w_cursor.col > mincol - && (curwin->w_cursor.lnum != Insstart_orig.lnum - || curwin->w_cursor.col != Insstart_orig.col))); + && (can_bs(BS_NOSTOP) + || (curwin->w_cursor.lnum != Insstart_orig.lnum + || curwin->w_cursor.col != Insstart_orig.col)))); } did_backspace = true; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8a1556320c..a25b140b2f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -365,7 +365,7 @@ void eval_init(void) eval_msgpack_type_lists[i] = type_list; if (tv_dict_add(msgpack_types_dict, di) == FAIL) { // There must not be duplicate items in this dictionary by definition. - assert(false); + abort(); } } msgpack_types_dict->dv_lock = VAR_FIXED; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 638fef331a..bd4dc87d31 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -147,7 +147,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, tv_clear(&key.val); if (tv_dict_add(last_container.container.vval.v_dict, obj_di) == FAIL) { - assert(false); + abort(); } obj_di->di_tv = obj.val; } else { @@ -480,7 +480,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, break; } default: { - assert(false); + abort(); } } } else { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 9a9f2e4287..a4d7af7971 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -174,7 +174,7 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, case kMPConvPartial: { switch (v.data.p.stage) { case kMPConvPartialArgs: { - assert(false); + abort(); break; } case kMPConvPartialSelf: { @@ -237,7 +237,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, char *const buf = xmalloc(len); size_t read_bytes; if (encode_read_from_list(&lrstate, buf, len, &read_bytes) != OK) { - assert(false); + abort(); } assert(len == read_bytes); *ret_buf = buf; diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index da05ecda43..bbba9d12f2 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -118,7 +118,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, return OK; } case VAR_UNKNOWN: { - assert(false); + abort(); } } } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8235d74cbb..1a7bbf2d0e 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1798,7 +1798,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_copy_fullenv(env, env_size); - for (size_t i = 0; i < env_size; i++) { + for (ssize_t i = env_size - 1; i >= 0; i--) { const char * str = env[i]; const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), '='); @@ -1806,6 +1806,12 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) ptrdiff_t len = end - str; assert(len > 0); const char * value = str + len + 1; + if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) { + // Since we're traversing from the end of the env block to the front, any + // duplicate names encountered should be ignored. This preserves the + // semantics of env vars defined later in the env block taking precedence. + continue; + } tv_dict_add_str(rettv->vval.v_dict, str, len, value); @@ -3301,7 +3307,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; case kCdScopeInvalid: // We should never get here - assert(false); + abort(); } if (from) { @@ -4354,7 +4360,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; case kCdScopeInvalid: // We should never get here - assert(false); + abort(); } } @@ -4875,6 +4881,95 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } +static const char *ignored_env_vars[] = { +#ifndef WIN32 + "COLUMNS", + "LINES", + "TERMCAP", + "COLORFGBG", +#endif + NULL +}; + +/// According to comments in src/win/process.c of libuv, Windows has a few +/// "essential" environment variables. +static const char *required_env_vars[] = { +#ifdef WIN32 + "HOMEDRIVE", + "HOMEPATH", + "LOGONSERVER", + "PATH", + "SYSTEMDRIVE", + "SYSTEMROOT", + "TEMP", + "USERDOMAIN", + "USERNAME", + "USERPROFILE", + "WINDIR", +#endif + NULL +}; + +static dict_T *create_environment(const dictitem_T *job_env, + const bool clear_env, + const bool pty) +{ + dict_T * env = tv_dict_alloc(); + + if (!clear_env) { + typval_T temp_env = TV_INITIAL_VALUE; + f_environ(NULL, &temp_env, NULL); + tv_dict_extend(env, temp_env.vval.v_dict, "force"); + tv_dict_free(temp_env.vval.v_dict); + + if (pty) { + // These environment variables generally shouldn't be propagated to the + // child process. We're removing them here so the user can still decide + // they want to explicitly set them. + for (size_t i = 0; + i < ARRAY_SIZE(ignored_env_vars) && ignored_env_vars[i]; + i++) { + dictitem_T *dv = tv_dict_find(env, ignored_env_vars[i], -1); + if (dv) { + tv_dict_item_remove(env, dv); + } + } +#ifndef WIN32 + // Set COLORTERM to "truecolor" if termguicolors is set and 256 + // otherwise, but only if it was set in the parent terminal at all + dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); + if (dv) { + tv_dict_item_remove(env, dv); + tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256"); + } +#endif + } + } + + if (job_env) { + tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force"); + } + + if (pty) { + // Now that the custom environment is configured, we need to ensure certain + // environment variables are present. + for (size_t i = 0; + i < ARRAY_SIZE(required_env_vars) && required_env_vars[i]; + i++) { + size_t len = strlen(required_env_vars[i]); + dictitem_T *dv = tv_dict_find(env, required_env_vars[i], len); + if (!dv) { + const char *env_var = os_getenv(required_env_vars[i]); + if (env_var) { + tv_dict_add_str(env, required_env_vars[i], len, env_var); + } + } + } + } + + return env; +} + // "jobstart()" function static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4887,7 +4982,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool executable = true; char **argv = tv_to_argv(&argvars[0], NULL, &executable); - char **env = NULL; + dict_T *env = NULL; if (!argv) { rettv->vval.v_number = executable ? 0 : -1; return; // Did error message in tv_to_argv. @@ -4911,6 +5006,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; char *cwd = NULL; + dictitem_T *job_env = NULL; if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; @@ -4936,7 +5032,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) #endif char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && strlen(new_cwd) > 0) { + if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. if (!os_isdir_executable((const char *)cwd)) { @@ -4945,52 +5041,22 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env) { - if (job_env->di_tv.v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - - if (clear_env) { - // + 1 for last null entry - env = xmalloc((custom_env_size + 1) * sizeof(*env)); - env_size = 0; - } else { - env_size = os_get_fullenv_size(); - env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - - os_copy_fullenv(env, env_size); - i = env_size; - } - assert(env); // env must be allocated at this point - - TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { - const char *str = tv_get_string(&var->di_tv); - assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; - env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); - i++; - }); - - // must be null terminated - env[env_size + custom_env_size] = NULL; + job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env && job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; } - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; } } + env = create_environment(job_env, clear_env, pty); + uint16_t width = 0, height = 0; char *term_name = NULL; @@ -10518,6 +10584,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Callback on_exit = CALLBACK_NONE; dict_T *job_opts = NULL; const char *cwd = "."; + dict_T *env = NULL; + const bool pty = true; + bool clear_env = false; + dictitem_T *job_env = NULL; + if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; @@ -10532,17 +10603,31 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env && job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; + } + + clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; } } + env = create_environment(job_env, clear_env, pty); + + const bool rpc = false; + const bool overlapped = false; + const bool detach = false; uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, - true, false, false, false, cwd, + pty, rpc, overlapped, detach, cwd, term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), NULL, + xstrdup("xterm-256color"), env, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 02d32a4f86..9be487f4fd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1523,6 +1523,33 @@ varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) return tv_get_number(&di->di_tv); } +/// Converts a dict to an environment +/// +/// +char **tv_dict_to_env(dict_T *denv) +{ + size_t env_size = (size_t)tv_dict_len(denv); + + size_t i = 0; + char **env = NULL; + + // + 1 for NULL + env = xmalloc((env_size + 1) * sizeof(*env)); + + TV_DICT_ITER(denv, var, { + const char *str = tv_get_string(&var->di_tv); + assert(str); + size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + env[i] = xmalloc(len); + snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + i++; + }); + + // must be null terminated + env[env_size] = NULL; + return env; +} + /// Get a string item from a dictionary /// /// @param[in] d Dictionary to get item from. @@ -2494,7 +2521,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) break; } case VAR_UNKNOWN: { - assert(false); + abort(); } } #undef CHANGE_LOCK @@ -2666,7 +2693,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, } } - assert(false); + abort(); return false; } @@ -2719,7 +2746,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2764,7 +2791,7 @@ bool tv_check_num(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2809,7 +2836,7 @@ bool tv_check_str(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 13517d3df1..0b1ecb12e2 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -41,7 +41,6 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; - uvproc->uvopts.env = proc->env; uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; @@ -49,6 +48,12 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[2].flags = UV_IGNORE; uvproc->uv.data = proc; + if (proc->env) { + uvproc->uvopts.env = tv_dict_to_env(proc->env); + } else { + uvproc->uvopts.env = NULL; + } + if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; #ifdef WIN32 @@ -78,6 +83,9 @@ int libuv_process_spawn(LibuvProcess *uvproc) int status; if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) { ELOG("uv_spawn failed: %s", uv_strerror(status)); + if (uvproc->uvopts.env) { + os_free_fullenv(uvproc->uvopts.env); + } return status; } @@ -97,6 +105,10 @@ static void close_cb(uv_handle_t *handle) if (proc->internal_close_cb) { proc->internal_close_cb(proc); } + LibuvProcess *uvproc = (LibuvProcess *)proc; + if (uvproc->uvopts.env) { + os_free_fullenv(uvproc->uvopts.env); + } } static void exit_cb(uv_process_t *handle, int64_t status, int term_signal) diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 84e81238e9..24debdb276 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -4,6 +4,7 @@ #include "nvim/event/loop.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +#include "nvim/eval/typval.h" typedef enum { kProcessTypeUv, @@ -23,7 +24,7 @@ struct process { uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; - char **env; + dict_T *env; Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 61e4d634c6..6d26c5f69c 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -790,7 +790,10 @@ void ex_retab(exarg_T *eap) for (col = 0; col < len; col++) { ptr[col] = (col < num_tabs) ? '\t' : ' '; } - ml_replace(lnum, new_line, false); + if (ml_replace(lnum, new_line, false) == OK) { + // "new_line" may have been copied + new_line = curbuf->b_ml.ml_line_ptr; + } if (first_line == 0) { first_line = lnum; } @@ -2413,7 +2416,10 @@ int do_ecmd( (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, false); - the_curwin->w_closing = false; + // Autocommands may have closed the window. + if (win_valid(the_curwin)) { + the_curwin->w_closing = false; + } buf->b_locked--; // autocmds may abort script processing @@ -3126,6 +3132,9 @@ static bool sub_joining_lines(exarg_T *eap, char_u *pat, char_u *sub, || *cmd == 'l' || *cmd == 'p' || *cmd == '#')))) { + if (eap->skip) { + return true; + } curwin->w_cursor.lnum = eap->line1; if (*cmd == 'l') { eap->flags = EXFLAG_LIST; @@ -3307,11 +3316,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int save_b_changed = curbuf->b_changed; bool preview = (State & CMDPREVIEW); - // inccommand tests fail without this check - if (!preview) { - // Required for Undo to work for extmarks. - u_save_cursor(); - } + bool did_save = false; if (!global_busy) { sub_nsubs = 0; @@ -3693,6 +3698,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, } else { char_u *orig_line = NULL; int len_change = 0; + const bool save_p_lz = p_lz; int save_p_fen = curwin->w_p_fen; curwin->w_p_fen = FALSE; @@ -3701,6 +3707,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int temp = RedrawingDisabled; RedrawingDisabled = 0; + // avoid calling update_screen() in vgetorpeek() + p_lz = false; + if (new_start != NULL) { /* There already was a substitution, we would * like to show this to the user. We cannot @@ -3754,7 +3763,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, /* clear the question */ msg_didout = FALSE; /* don't scroll up */ msg_col = 0; - gotocmdline(TRUE); + gotocmdline(true); + p_lz = save_p_lz; // restore the line if (orig_line != NULL) { @@ -3983,6 +3993,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int matchcols = end.col - ((end.lnum == start.lnum) ? start.col : 0); int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0); + if (!did_save) { + // Required for Undo to work for extmarks. + u_save_cursor(); + did_save = true; + } extmark_splice(curbuf, lnum_start-1, start_col, end.lnum-start.lnum, matchcols, replaced_bytes, lnum-lnum_start, subcols, sublen-1, kExtmarkUndo); diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 069d5d461b..e9046da800 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2455,7 +2455,7 @@ module.cmds = { }, { command='sleep', - flags=bit.bor(RANGE, COUNT, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, EXTRA, TRLBAR, CMDWIN), addr_type='ADDR_OTHER', func='ex_sleep', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c400975108..e49bb99aa0 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1639,10 +1639,10 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) if (wig) { i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND); + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); } else { i = gen_expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, - fcountp, fnamesp, EW_FILE|EW_NOTFOUND); + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); } ga_clear(&ga); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ccf7dd0f68..52da7fe4f6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -317,7 +317,9 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, int count = 0; /* line number count */ int did_inc = FALSE; /* incremented RedrawingDisabled */ int retval = OK; - cstack_T cstack; // conditional stack + cstack_T cstack = { // conditional stack + .cs_idx = -1, + }; garray_T lines_ga; // keep lines for ":while"/":for" int current_line = 0; // active line in lines_ga char_u *fname = NULL; // function or script name @@ -360,11 +362,6 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, call_depth++; start_batch_changes(); - cstack.cs_idx = -1; - cstack.cs_looplevel = 0; - cstack.cs_trylevel = 0; - cstack.cs_emsg_silent_list = NULL; - cstack.cs_lflags = 0; ga_init(&lines_ga, (int)sizeof(wcmd_T), 10); real_cookie = getline_cookie(fgetline, cookie); @@ -3041,9 +3038,10 @@ const char * set_one_cmd_context( p = arg + 1; arg = (const char *)skip_cmd_arg((char_u *)arg, false); - /* Still touching the command after '+'? */ - if (*arg == NUL) + // Still touching the command after '+'? + if (*arg == NUL) { return p; + } // Skip space(s) after +command to get to the real argument. arg = (const char *)skipwhite((const char_u *)arg); @@ -3680,6 +3678,10 @@ const char * set_one_cmd_context( xp->xp_pattern = (char_u *)arg; break; + case CMD_lua: + xp->xp_context = EXPAND_LUA; + break; + default: break; } @@ -3968,7 +3970,7 @@ static linenr_T get_address(exarg_T *eap, break; default: - if (ascii_isdigit(*cmd)) { // absolute line number + if (ascii_isdigit(*cmd)) { // absolute line number lnum = getdigits_long(&cmd, false, 0); } } @@ -5187,6 +5189,7 @@ static const char *command_complete[] = #ifdef HAVE_WORKING_LIBINTL [EXPAND_LOCALES] = "locale", #endif + [EXPAND_LUA] = "lua", [EXPAND_MAPCLEAR] = "mapclear", [EXPAND_MAPPINGS] = "mapping", [EXPAND_MENUS] = "menu", @@ -5400,8 +5403,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, size_t vallen = 0; size_t attrlen = len; - /* Look for the attribute name - which is the part before any '=' */ - for (i = 0; i < (int)len; ++i) { + // Look for the attribute name - which is the part before any '=' + for (i = 0; i < (int)len; i++) { if (attr[i] == '=') { val = &attr[i + 1]; vallen = len - i - 1; @@ -7503,8 +7506,9 @@ static void ex_read(exarg_T *eap) } if (*eap->arg == NUL) { - if (check_fname() == FAIL) /* check for no file name */ + if (check_fname() == FAIL) { // check for no file name return; + } i = readfile(curbuf->b_ffname, curbuf->b_fname, eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); } else { @@ -7587,7 +7591,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) curwin->w_localdir = (char_u *)xstrdup(cwd); break; case kCdScopeInvalid: - assert(false); + abort(); } shorten_fnames(true); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 626b840798..cf6bb6d492 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -69,6 +69,7 @@ #include "nvim/lib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/highlight_defs.h" +#include "nvim/lua/executor.h" #include "nvim/viml/parser/parser.h" #include "nvim/viml/parser/expressions.h" @@ -3828,7 +3829,7 @@ static void cmd_cursor_goto(int row, int col) ui_grid_cursor_goto(grid->handle, row, col); } -void gotocmdline(int clr) +void gotocmdline(bool clr) { if (ui_has(kUICmdline)) { return; @@ -3945,6 +3946,12 @@ nextwild ( p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), use_options, type); xfree(p1); + + // xp->xp_pattern might have been modified by ExpandOne (for example, + // in lua completion), so recompute the pattern index and length + i = (int)(xp->xp_pattern - ccline.cmdbuff); + xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; + // Longest match: make sure it is not shorter, happens with :help. if (p2 != NULL && type == WILD_LONGEST) { for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { @@ -3960,7 +3967,7 @@ nextwild ( } if (p2 != NULL && !got_int) { - difflen = (int)STRLEN(p2) - (int)xp->xp_pattern_len; + difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len); if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) { realloc_cmdbuff(ccline.cmdlen + difflen + 4); xp->xp_pattern = ccline.cmdbuff + i; @@ -5106,6 +5113,10 @@ ExpandFromContext ( if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } + if (xp->xp_context == EXPAND_LUA) { + ILOG("PAT %s", pat); + return nlua_expand_pat(xp, pat, num_file, file); + } regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); if (regmatch.regprog == NULL) @@ -6372,7 +6383,7 @@ int hist_type2char(int type) return '>'; } default: { - assert(false); + abort(); } } return NUL; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index b2d8532cd7..cacbeddb32 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -71,7 +71,8 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { /// @returns the mark id uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, int row, colnr_T col, int end_row, colnr_T end_col, - Decoration *decor, ExtmarkOp op) + Decoration *decor, bool right_gravity, + bool end_right_gravity, ExtmarkOp op) { ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); assert(ns != NULL); @@ -109,10 +110,10 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, if (end_row > -1) { mark = marktree_put_pair(buf->b_marktree, - row, col, true, - end_row, end_col, false); + row, col, right_gravity, + end_row, end_col, end_right_gravity); } else { - mark = marktree_put(buf->b_marktree, row, col, true); + mark = marktree_put(buf->b_marktree, row, col, right_gravity); } revised: diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index b1fa0b6779..8beba38509 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1595,7 +1595,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) } case kCdScopeInvalid: { // Should never happen. - assert(false); + abort(); } } diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 0593c16999..5032646d7e 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2999,7 +2999,6 @@ static void foldlevelDiff(fline_T *flp) static void foldlevelExpr(fline_T *flp) { win_T *win; - int n; int c; linenr_T lnum = flp->lnum + flp->off; @@ -3017,7 +3016,7 @@ static void foldlevelExpr(fline_T *flp) /* KeyTyped may be reset to 0 when calling a function which invokes * do_cmdline(). To make 'foldopen' work correctly restore KeyTyped. */ const bool save_keytyped = KeyTyped; - n = (int)eval_foldexpr(flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; switch (c) { @@ -3202,8 +3201,10 @@ int put_folds(FILE *fd, win_T *wp) { if (foldmethodIsManual(wp)) { if (put_line(fd, "silent! normal! zE") == FAIL - || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL) + || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL + || put_line(fd, "let &fdl = &fdl") == FAIL) { return FAIL; + } } /* If some folds are manually opened/closed, need to restore that. */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 3b8f4116b7..22f06941aa 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1009,6 +1009,8 @@ EXTERN char_u e_floatonly[] INIT(=N_( EXTERN char_u e_floatexchange[] INIT(=N_( "E5602: Cannot exchange or rotate float")); +EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_( + "E1155: Cannot define autocommands for ALL events")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); diff --git a/src/nvim/log.c b/src/nvim/log.c index 19203a3c2a..324382a0f7 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -51,7 +51,7 @@ static bool log_try_create(char *fname) /// Initializes path to log file. Sets $NVIM_LOG_FILE if empty. /// -/// Tries $NVIM_LOG_FILE, or falls back to $XDG_DATA_HOME/nvim/log. Path to log +/// Tries $NVIM_LOG_FILE, or falls back to $XDG_CACHE_HOME/nvim/log. Path to log /// file is cached, so only the first call has effect, unless first call was not /// successful. Failed initialization indicates either a bug in expand_env() /// or both $NVIM_LOG_FILE and $HOME environment variables are undefined. @@ -69,8 +69,16 @@ static bool log_path_init(void) || log_file_path[0] == '\0' || os_isdir((char_u *)log_file_path) || !log_try_create(log_file_path)) { + // Make kXDGCacheHome if it does not exist. + char *cachehome = get_xdg_home(kXDGCacheHome); + char *failed_dir = NULL; + bool log_dir_failure = false; + if (!os_isdir((char_u *)cachehome)) { + log_dir_failure = (os_mkdir_recurse(cachehome, 0700, &failed_dir) != 0); + } + XFREE_CLEAR(cachehome); // Invalid $NVIM_LOG_FILE or failed to expand; fall back to default. - char *defaultpath = stdpaths_user_data_subpath("log", 0, true); + char *defaultpath = stdpaths_user_cache_subpath("log"); size_t len = xstrlcpy(log_file_path, defaultpath, size); xfree(defaultpath); // Fall back to .nvimlog @@ -83,6 +91,11 @@ static bool log_path_init(void) return false; } os_setenv(LOG_FILE_ENV, log_file_path, true); + if (log_dir_failure) { + WLOG("Failed to create directory %s for writing logs: %s", + failed_dir, os_strerror(log_dir_failure)); + } + XFREE_CLEAR(failed_dir); } return true; } @@ -323,4 +336,3 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, return true; } - diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 030df69caa..83b3729ad3 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -245,7 +245,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { - assert(false); + abort(); } kv_push(stack, cur); cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; @@ -391,7 +391,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } default: { - assert(false); + abort(); } } nlua_pop_typval_table_processing_end: @@ -1200,7 +1200,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) break; } default: { - assert(false); + abort(); } } break; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 344a2387d6..310b194c8c 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -5,6 +5,7 @@ #include <lualib.h> #include <lauxlib.h> +#include "nvim/version.h" #include "nvim/misc1.h" #include "nvim/getchar.h" #include "nvim/garray.h" @@ -78,6 +79,17 @@ static void nlua_error(lua_State *const lstate, const char *const msg) lua_pop(lstate, 1); } +/// Return version of current neovim build +/// +/// @param lstate Lua interpreter state. +static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + Dictionary version = version_dict(); + nlua_push_Dictionary(lstate, version, true); + api_free_dictionary(version); + return 1; +} + /// Compare two strings, ignoring case /// /// Expects two values on the stack: compared strings. Returns one of the @@ -420,6 +432,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // str_byteindex lua_pushcfunction(lstate, &nlua_str_byteindex); lua_setfield(lstate, -2, "str_byteindex"); + // neovim version + lua_pushcfunction(lstate, &nlua_nvim_version); + lua_setfield(lstate, -2, "version"); // schedule lua_pushcfunction(lstate, &nlua_schedule); lua_setfield(lstate, -2, "schedule"); @@ -1277,6 +1292,80 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "_ts_parse_query"); } +int nlua_expand_pat(expand_T *xp, + char_u *pat, + int *num_results, + char_u ***results) +{ + lua_State *const lstate = nlua_enter(); + int ret = OK; + + // [ vim ] + lua_getglobal(lstate, "vim"); + + // [ vim, vim._expand_pat ] + lua_getfield(lstate, -1, "_expand_pat"); + luaL_checktype(lstate, -1, LUA_TFUNCTION); + + // [ vim, vim._log_keystroke, buf ] + lua_pushlstring(lstate, (const char *)pat, STRLEN(pat)); + + if (lua_pcall(lstate, 1, 2, 0) != 0) { + nlua_error( + lstate, + _("Error executing vim._expand_pat: %.*s")); + return FAIL; + } + + Error err = ERROR_INIT; + + *num_results = 0; + *results = NULL; + + int prefix_len = (int)nlua_pop_Integer(lstate, &err); + if (ERROR_SET(&err)) { + ret = FAIL; + goto cleanup; + } + + Array completions = nlua_pop_Array(lstate, &err); + if (ERROR_SET(&err)) { + ret = FAIL; + goto cleanup_array; + } + + garray_T result_array; + ga_init(&result_array, (int)sizeof(char *), 80); + for (size_t i = 0; i < completions.size; i++) { + Object v = completions.items[i]; + + if (v.type != kObjectTypeString) { + ret = FAIL; + goto cleanup_array; + } + + GA_APPEND( + char_u *, + &result_array, + vim_strsave((char_u *)v.data.string.data)); + } + + xp->xp_pattern += prefix_len; + *results = result_array.ga_data; + *num_results = result_array.ga_len; + +cleanup_array: + api_free_array(completions); + +cleanup: + + if (ret == FAIL) { + ga_clear(&result_array); + } + + return ret; +} + static int nlua_regex(lua_State *lstate) { Error err = ERROR_INIT; diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b20fbbf038..dbf4f6014c 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -121,10 +121,17 @@ function vim._load_package(name) end for _,trail in ipairs(vim._so_trails) do - local path = "lua/"..trail:gsub('?',basename) + local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash local found = vim.api.nvim_get_runtime_file(path, false) if #found > 0 then - local f, err = package.loadlib(found[1]) + -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is + -- a) strip prefix up to and including the first dash, if any + -- b) replace all dots by underscores + -- c) prepend "luaopen_" + -- So "foo-bar.baz" should result in "luaopen_bar_baz" + local dash = name:find("-", 1, true) + local modname = dash and name:sub(dash + 1) or name + local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_")) return f or error(err) end end @@ -527,4 +534,164 @@ function vim._log_keystroke(char) end end +--- Generate a list of possible completions for the string. +--- String starts with ^ and then has the pattern. +--- +--- 1. Can we get it to just return things in the global namespace with that name prefix +--- 2. Can we get it to return things from global namespace even with `print(` in front. +function vim._expand_pat(pat, env) + env = env or _G + + pat = string.sub(pat, 2, #pat) + + if pat == '' then + local result = vim.tbl_keys(env) + table.sort(result) + return result, 0 + end + + -- TODO: We can handle spaces in [] ONLY. + -- We should probably do that at some point, just for cooler completion. + -- TODO: We can suggest the variable names to go in [] + -- This would be difficult as well. + -- Probably just need to do a smarter match than just `:match` + + -- Get the last part of the pattern + local last_part = pat:match("[%w.:_%[%]'\"]+$") + if not last_part then return {}, 0 end + + local parts, search_index = vim._expand_pat_get_parts(last_part) + + local match_part = string.sub(last_part, search_index, #last_part) + local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or '' + + local final_env = env + + for _, part in ipairs(parts) do + if type(final_env) ~= 'table' then + return {}, 0 + end + local key + + -- Normally, we just have a string + -- Just attempt to get the string directly from the environment + if type(part) == "string" then + key = part + else + -- However, sometimes you want to use a variable, and complete on it + -- With this, you have the power. + + -- MY_VAR = "api" + -- vim[MY_VAR] + -- -> _G[MY_VAR] -> "api" + local result_key = part[1] + if not result_key then + return {}, 0 + end + + local result = rawget(env, result_key) + + if result == nil then + return {}, 0 + end + + key = result + end + local field = rawget(final_env, key) + if field == nil then + local mt = getmetatable(final_env) + if mt and type(mt.__index) == "table" then + field = rawget(mt.__index, key) + end + end + final_env = field + + if not final_env then + return {}, 0 + end + end + + local keys = {} + local function insert_keys(obj) + for k,_ in pairs(obj) do + if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then + table.insert(keys,k) + end + end + end + + if type(final_env) == "table" then + insert_keys(final_env) + end + local mt = getmetatable(final_env) + if mt and type(mt.__index) == "table" then + insert_keys(mt.__index) + end + + table.sort(keys) + + return keys, #prefix_match_pat +end + +vim._expand_pat_get_parts = function(lua_string) + local parts = {} + + local accumulator, search_index = '', 1 + local in_brackets, bracket_end = false, -1 + local string_char = nil + for idx = 1, #lua_string do + local s = lua_string:sub(idx, idx) + + if not in_brackets and (s == "." or s == ":") then + table.insert(parts, accumulator) + accumulator = '' + + search_index = idx + 1 + elseif s == "[" then + in_brackets = true + + table.insert(parts, accumulator) + accumulator = '' + + search_index = idx + 1 + elseif in_brackets then + if idx == bracket_end then + in_brackets = false + search_index = idx + 1 + + if string_char == "VAR" then + table.insert(parts, { accumulator }) + accumulator = '' + + string_char = nil + end + elseif not string_char then + bracket_end = string.find(lua_string, ']', idx, true) + + if s == '"' or s == "'" then + string_char = s + elseif s ~= ' ' then + string_char = "VAR" + accumulator = s + end + elseif string_char then + if string_char ~= s then + accumulator = accumulator .. s + else + table.insert(parts, accumulator) + accumulator = '' + + string_char = nil + end + end + else + accumulator = accumulator .. s + end + end + + parts = vim.tbl_filter(function(val) return #val > 0 end, parts) + + return parts, search_index +end + return module diff --git a/src/nvim/main.c b/src/nvim/main.c index 8bf745966e..9f71df3a46 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -2044,7 +2044,6 @@ static void usage(void) mch_msg(_(" -u <config> Use this config file\n")); mch_msg(_(" -v, --version Print version information\n")); mch_msg(_(" -V[N][file] Verbose [level][file]\n")); - mch_msg(_(" -Z Restricted mode\n")); mch_msg("\n"); mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 45ca097033..73a9c1d1d7 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -777,6 +777,7 @@ void ex_delmarks(exarg_T *eap) n = i - 'A'; } namedfm[n].fmark.mark.lnum = 0; + namedfm[n].fmark.fnum = 0; XFREE_CLEAR(namedfm[n].fname); } } diff --git a/src/nvim/message.c b/src/nvim/message.c index f94529c687..ba7a667a60 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -890,6 +890,40 @@ char_u *msg_may_trunc(int force, char_u *s) return s; } +void clear_hl_msg(HlMessage *hl_msg) +{ + for (size_t i = 0; i < kv_size(*hl_msg); i++) { + xfree(kv_A(*hl_msg, i).text.data); + } + kv_destroy(*hl_msg); + *hl_msg = (HlMessage)KV_INITIAL_VALUE; +} + +#define LINE_BUFFER_SIZE 4096 + +void add_hl_msg_hist(HlMessage hl_msg) +{ + // TODO(notomo): support multi highlighted message history + size_t pos = 0; + char buf[LINE_BUFFER_SIZE]; + for (uint32_t i = 0; i < kv_size(hl_msg); i++) { + HlMessageChunk chunk = kv_A(hl_msg, i); + for (uint32_t j = 0; j < chunk.text.size; j++) { + if (pos == LINE_BUFFER_SIZE - 1) { + buf[pos] = NUL; + add_msg_hist((const char *)buf, -1, MSG_HIST, true); + pos = 0; + continue; + } + buf[pos++] = chunk.text.data[j]; + } + } + if (pos != 0) { + buf[pos] = NUL; + add_msg_hist((const char *)buf, -1, MSG_HIST, true); + } +} + /// @param[in] len Length of s or -1. static void add_msg_hist(const char *s, int len, int attr, bool multiline) { diff --git a/src/nvim/message.h b/src/nvim/message.h index fdb9bc96ca..377c725fa1 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -8,6 +8,8 @@ #include "nvim/macros.h" #include "nvim/types.h" #include "nvim/grid_defs.h" +#include "nvim/api/private/defs.h" +#include "nvim/lib/kvec.h" /* * Types of dialogs passed to do_dialog(). @@ -75,6 +77,13 @@ /// Like #MSG_PUTS_ATTR, but if middle part of long messages will be replaced #define MSG_PUTS_LONG_ATTR(s, a) msg_puts_long_attr((char_u *)(s), (a)) +typedef struct { + String text; + int attr; +} HlMessageChunk; + +typedef kvec_t(HlMessageChunk) HlMessage; + /// Message history for `:messages` typedef struct msg_hist { struct msg_hist *next; ///< Next message. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4e955667dc..8f22243348 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3978,16 +3978,19 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) curwin->w_curswant -= width2; } else { // to previous line + + // Move to the start of a closed fold. Don't do that when + // 'foldopen' contains "all": it will open in a moment. + if (!(fdo_flags & FDO_ALL)) { + (void)hasFolding(curwin->w_cursor.lnum, + &curwin->w_cursor.lnum, NULL); + } if (curwin->w_cursor.lnum == 1) { retval = false; break; } - --curwin->w_cursor.lnum; - /* Move to the start of a closed fold. Don't do that when - * 'foldopen' contains "all": it will open in a moment. */ - if (!(fdo_flags & FDO_ALL)) - (void)hasFolding(curwin->w_cursor.lnum, - &curwin->w_cursor.lnum, NULL); + curwin->w_cursor.lnum--; + linelen = linetabsize(get_cursor_line_ptr()); if (linelen > width1) { int w = (((linelen - width1 - 1) / width2) + 1) * width2; @@ -6708,11 +6711,8 @@ static void nv_g_cmd(cmdarg_T *cap) */ case 'j': case K_DOWN: - /* with 'nowrap' it works just like the normal "j" command; also when - * in a closed fold */ - if (!curwin->w_p_wrap - || hasFolding(curwin->w_cursor.lnum, NULL, NULL) - ) { + // with 'nowrap' it works just like the normal "j" command. + if (!curwin->w_p_wrap) { oap->motion_type = kMTLineWise; i = cursor_down(cap->count1, oap->op_type == OP_NOP); } else @@ -6723,11 +6723,8 @@ static void nv_g_cmd(cmdarg_T *cap) case 'k': case K_UP: - /* with 'nowrap' it works just like the normal "k" command; also when - * in a closed fold */ - if (!curwin->w_p_wrap - || hasFolding(curwin->w_cursor.lnum, NULL, NULL) - ) { + // with 'nowrap' it works just like the normal "k" command. + if (!curwin->w_p_wrap) { oap->motion_type = kMTLineWise; i = cursor_up(cap->count1, oap->op_type == OP_NOP); } else diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 052b07ed44..87d092281a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2623,7 +2623,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } // NOTREACHED case kMTUnknown: - assert(false); + abort(); } } @@ -6092,7 +6092,7 @@ static void set_clipboard(int name, yankreg_T *reg) break; } case kMTUnknown: { - assert(false); + abort(); } } diff --git a/src/nvim/option.c b/src/nvim/option.c index d43dd9ba15..7cc83399e5 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -306,7 +306,7 @@ static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix", static char *(p_bufhidden_values[]) = { "hide", "unload", "delete", "wipe", NULL }; -static char *(p_bs_values[]) = { "indent", "eol", "start", NULL }; +static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL }; static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent", "syntax", "diff", NULL }; static char *(p_fcl_values[]) = { "all", NULL }; @@ -1366,6 +1366,10 @@ int do_set( *(char_u **)varp = vim_strsave( (char_u *)"indent,eol,start"); break; + case 3: + *(char_u **)varp = vim_strsave( + (char_u *)"indent,eol,nostop"); + break; } xfree(oldval); if (origval == oldval) { @@ -1938,6 +1942,7 @@ static void didset_options(void) (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); + (void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); @@ -2909,7 +2914,7 @@ ambw_end: #endif } else if (varp == &curwin->w_p_scl) { // 'signcolumn' - if (check_opt_strings(*varp, p_scl_values, false) != OK) { + if (check_signcolumn(*varp) != OK) { errmsg = e_invarg; } // When changing the 'signcolumn' to or from 'number', recompute the @@ -2939,7 +2944,7 @@ ambw_end: } } else if (varp == &p_bs) { // 'backspace' if (ascii_isdigit(*p_bs)) { - if (*p_bs >'2' || p_bs[1] != NUL) { + if (*p_bs > '3' || p_bs[1] != NUL) { errmsg = e_invarg; } } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { @@ -3073,6 +3078,10 @@ ambw_end: if (!parse_winhl_opt(curwin)) { errmsg = e_invarg; } + } else if (varp == &p_tpf) { + if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { + errmsg = e_invarg; + } } else { // Options that are a list of flags. p = NULL; @@ -3228,6 +3237,34 @@ static int int_cmp(const void *a, const void *b) return *(const int *)a - *(const int *)b; } +/// Handle setting 'signcolumn' for value 'val' +/// +/// @return OK when the value is valid, FAIL otherwise +int check_signcolumn(char_u *val) +{ + // check for basic match + if (check_opt_strings(val, p_scl_values, false) == OK) { + return OK; + } + + // check for 'auto:<NUMBER>-<NUMBER>' + if (STRLEN(val) == 8 + && !STRNCMP(val, "auto:", 5) + && ascii_isdigit(val[5]) + && val[6] == '-' + && ascii_isdigit(val[7]) + ) { + int min = val[5] - '0'; + int max = val[7] - '0'; + if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) { + return FAIL; + } + return OK; + } + + return FAIL; +} + /// Handle setting 'colorcolumn' or 'textwidth' in window "wp". /// /// @return error message, NULL if it's OK. @@ -6801,15 +6838,15 @@ static int check_opt_wim(void) } /// Check if backspacing over something is allowed. -/// The parameter what is one of the following: whatBS_INDENT, BS_EOL -/// or BS_START +/// @param what BS_INDENT, BS_EOL, BS_START, or BS_NOSTOP bool can_bs(int what) { if (what == BS_START && bt_prompt(curbuf)) { return false; } switch (*p_bs) { - case '2': return true; + case '3': return true; + case '2': return what != BS_NOSTOP; case '1': return what != BS_START; case '0': return false; } @@ -7091,7 +7128,7 @@ int csh_like_shell(void) /// buffer signs and on user configuration. int win_signcol_count(win_T *wp) { - int maximum = 1, needed_signcols; + int minimum = 0, maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; // Note: It checks "no" or "number" in 'signcolumn' option @@ -7115,9 +7152,14 @@ int win_signcol_count(win_T *wp) if (!strncmp(scl, "auto:", 5)) { // Variable depending on a configuration maximum = scl[5] - '0'; + // auto:<NUM>-<NUM> + if (strlen(scl) == 8 && *(scl + 6) == '-') { + minimum = maximum; + maximum = scl[7] - '0'; + } } - return MIN(maximum, needed_signcols); + return MAX(minimum, MIN(maximum, needed_signcols)); } /// Get window or buffer local options diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index ec2160d365..43b0107800 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -282,9 +282,14 @@ enum { #define WIM_BUFLASTUSED 8 // arguments for can_bs() +// each defined char should be unique over all values +// except for BS_START, that intentionally also matches BS_NOSTOP +// because BS_NOSTOP behaves exactly the same except it +// does not stop at the start of the insert point #define BS_INDENT 'i' // "Indent" -#define BS_EOL 'o' // "eOl" +#define BS_EOL 'l' // "eoL" #define BS_START 's' // "Start" +#define BS_NOSTOP 'p' // "nostoP #define LISPWORD_VALUE \ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object" @@ -616,6 +621,19 @@ EXTERN int p_sta; // 'smarttab' EXTERN int p_sb; // 'splitbelow' EXTERN long p_tpm; // 'tabpagemax' EXTERN char_u *p_tal; // 'tabline' +EXTERN char_u *p_tpf; // 'termpastefilter' +EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' +#ifdef IN_OPTION_C +static char *(p_tpf_values[]) = + { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; +#endif +# define TPF_BS 0x001 +# define TPF_HT 0x002 +# define TPF_FF 0x004 +# define TPF_ESC 0x008 +# define TPF_DEL 0x010 +# define TPF_C0 0x020 +# define TPF_C1 0x040 EXTERN char_u *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index df2bfbce34..fe108ef1cc 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2821,6 +2821,14 @@ return { defaults={if_true={vi=false}} }, { + full_name='termpastefilter', abbreviation='tpf', + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, + vim=true, + varname='p_tpf', + defaults={if_true={vi="", vim="BS,HT,ESC,DEL"}} + }, + { full_name='terse', short_desc=N_("hides notification of search wrap"), type='bool', scope={'global'}, diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 879266e3d4..008f5ef63b 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -394,13 +394,21 @@ void os_get_hostname(char *hostname, size_t size) } /// To get the "real" home directory: -/// - get value of $HOME +/// 1. get value of $HOME +/// 2. if $HOME is not set, try the following +/// For Windows: +/// 1. assemble homedir using HOMEDRIVE and HOMEPATH +/// 2. try os_homedir() +/// 3. resolve a direct reference to another system variable +/// 4. guess C drive /// For Unix: -/// - go to that directory -/// - do os_dirname() to get the real name of that directory. -/// This also works with mounts and links. -/// Don't do this for Windows, it will change the "current dir" for a drive. +/// 1. try os_homedir() +/// 2. go to that directory +/// This also works with mounts and links. +/// Don't do this for Windows, it will change the "current dir" for a drive. +/// 3. fall back to current working directory as a last resort static char *homedir = NULL; +static char *os_homedir(void); void init_homedir(void) { @@ -430,7 +438,7 @@ void init_homedir(void) } } if (var == NULL) { - var = os_getenv("USERPROFILE"); + var = os_homedir(); } // Weird but true: $HOME may contain an indirect reference to another @@ -440,6 +448,7 @@ void init_homedir(void) const char *p = strchr(var + 1, '%'); if (p != NULL) { vim_snprintf(os_buf, (size_t)(p - var), "%s", var + 1); + var = NULL; const char *exp = os_getenv(os_buf); if (exp != NULL && *exp != NUL && STRLEN(exp) + STRLEN(p) < MAXPATHL) { @@ -458,8 +467,12 @@ void init_homedir(void) } #endif - if (var != NULL) { #ifdef UNIX + if (var == NULL) { + var = os_homedir(); + } + + if (var != NULL) { // Change to the directory and get the actual path. This resolves // links. Don't do it when we can't return. if (os_dirname((char_u *)os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { @@ -470,11 +483,37 @@ void init_homedir(void) EMSG(_(e_prev_dir)); } } + } + + // Fall back to current working directory if home is not found + if ((var == NULL || *var == NUL) + && os_dirname((char_u *)os_buf, sizeof(os_buf)) == OK) { + var = os_buf; + } #endif + if (var != NULL) { homedir = xstrdup(var); } } +static char homedir_buf[MAXPATHL]; + +static char *os_homedir(void) +{ + homedir_buf[0] = NUL; + size_t homedir_size = MAXPATHL; + uv_mutex_lock(&mutex); + // http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir + int ret_value = uv_os_homedir(homedir_buf, &homedir_size); + uv_mutex_unlock(&mutex); + if (ret_value == 0 && homedir_size < MAXPATHL) { + return homedir_buf; + } + ELOG("uv_os_homedir() failed %d: %s", ret_value, os_strerror(ret_value)); + homedir_buf[0] = NUL; + return NULL; +} + #if defined(EXITFREE) void free_homedir(void) diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 4d7d9a45df..348a139e79 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -20,6 +20,10 @@ # include <pty.h> #endif +#ifdef __APPLE__ +# include <crt_externs.h> +#endif + #include <uv.h> #include "nvim/lib/klist.h" @@ -154,28 +158,14 @@ void pty_process_teardown(Loop *loop) static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { +#if defined(HAVE__NSGETENVIRON) +#define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif // New session/process-group. #6530 setsid(); - os_unsetenv("COLUMNS"); - os_unsetenv("LINES"); - os_unsetenv("TERMCAP"); - os_unsetenv("COLORFGBG"); - // setting COLORTERM to "truecolor" if termguicolors is set and 256 - // otherwise, but only if it was set in the parent terminal at all - if (os_env_exists("COLORTERM")) { - const char *colorterm = os_getenv("COLORTERM"); - if (colorterm != NULL) { - if (p_tgc) { - os_setenv("COLORTERM", "truecolor", 1); - } else { - os_setenv("COLORTERM", "256", 1); - } - } else { - os_unsetenv("COLORTERM"); - } - } - signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); @@ -190,9 +180,14 @@ static void init_child(PtyProcess *ptyproc) } char *prog = ptyproc->process.argv[0]; - os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); - execvp(prog, ptyproc->process.argv); + + assert(proc->env); + tv_dict_add_str(proc->env, S_LEN("TERM"), + ptyproc->term_name ? ptyproc->term_name : "ansi"); + environ = tv_dict_to_env(proc->env); + execvp(prog, proc->argv); ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 6f7100e846..52d2f84ace 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -52,6 +52,7 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_connect_t *out_req = NULL; wchar_t *cmd_line = NULL; wchar_t *cwd = NULL; + wchar_t *env = NULL; const char *emsg = NULL; assert(proc->err.closed); @@ -124,13 +125,22 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } + if (proc->env != NULL) { + status = build_env_block(proc->env, &env); + } + + if (status != 0) { + emsg = "build_env_block failed"; + goto cleanup; + } + if (ptyproc->type == kConpty) { if (!os_conpty_spawn(conpty_object, &process_handle, NULL, cmd_line, cwd, - NULL)) { + env)) { emsg = "os_conpty_spawn failed"; status = (int)GetLastError(); goto cleanup; @@ -141,7 +151,7 @@ int pty_process_spawn(PtyProcess *ptyproc) NULL, // Optional application name cmd_line, cwd, - NULL, // Optional environment variables + env, &err); if (spawncfg == NULL) { emsg = "winpty_spawn_config_new failed"; @@ -213,6 +223,7 @@ cleanup: xfree(in_req); xfree(out_req); xfree(cmd_line); + xfree(env); xfree(cwd); return status; } @@ -454,3 +465,66 @@ int translate_winpty_error(int winpty_errno) default: return UV_UNKNOWN; } } + +typedef struct EnvNode { + wchar_t *str; + size_t len; + QUEUE node; +} EnvNode; + +/// Build the environment block to pass to CreateProcessW. +/// +/// @param[in] denv Dict of environment name/value pairs +/// @param[out] env Allocated environment block +/// +/// @returns zero on success or error code of MultiByteToWideChar function. +static int build_env_block(dict_T *denv, wchar_t **env_block) +{ + const size_t denv_size = (size_t)tv_dict_len(denv); + size_t env_block_len = 0; + int rc; + char **env = tv_dict_to_env(denv); + + QUEUE *q; + QUEUE env_q; + QUEUE_INIT(&env_q); + // Convert env vars to wchar_t and calculate how big the final env block + // needs to be + for (size_t i = 0; i < denv_size; i++) { + EnvNode *env_node = xmalloc(sizeof(*env_node)); + rc = utf8_to_utf16(env[i], -1, &env_node->str); + if (rc != 0) { + goto cleanup; + } + env_node->len = wcslen(env_node->str) + 1; + env_block_len += env_node->len; + QUEUE_INSERT_TAIL(&env_q, &env_node->node); + } + + // Additional '\0' after the final entry + env_block_len++; + + *env_block = xmalloc(sizeof(**env_block) * env_block_len); + wchar_t *pos = *env_block; + + QUEUE_FOREACH(q, &env_q) { + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + memcpy(pos, env_node->str, env_node->len * sizeof(*pos)); + pos += env_node->len; + } + + *pos = L'\0'; + +cleanup: + q = QUEUE_HEAD(&env_q); + while (q != &env_q) { + QUEUE *next = q->next; + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + XFREE_CLEAR(env_node->str); + QUEUE_REMOVE(q); + xfree(env_node); + q = next; + } + + return rc; +} diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 66bc990402..93b8d5ca12 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -108,6 +108,17 @@ char *get_xdg_home(const XDGVarType idx) return dir; } +/// Return subpath of $XDG_CACHE_HOME +/// +/// @param[in] fname New component of the path. +/// +/// @return [allocated] `$XDG_CACHE_HOME/nvim/{fname}` +char *stdpaths_user_cache_subpath(const char *fname) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + return concat_fnames_realloc(get_xdg_home(kXDGCacheHome), fname, true); +} + /// Return subpath of $XDG_CONFIG_HOME /// /// @param[in] fname New component of the path. diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 4b6533cd0c..5cf628935f 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -97,7 +97,7 @@ void os_microdelay(uint64_t us, bool ignoreinput) const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); if (0 != rv && UV_ETIMEDOUT != rv) { - assert(false); + abort(); break; } // Else: Timeout proceeded normally. diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index fa6674469c..db44f50a46 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -3290,10 +3290,6 @@ msgstr "-o[N]\t\tMaak N vensters oop (verstek: een vir elke ler)" #~ msgid " -V[N][file] Verbose [level][file]\n" #~ msgstr "" -#, fuzzy -#~ msgid " -Z Restricted mode\n" -#~ msgstr " vir twee modusse " - #~ msgid " --api-info Write msgpack-encoded API metadata to stdout\n" #~ msgstr "" @@ -7473,9 +7469,6 @@ msgstr "E446: Geen lernaam onder loper" #~ msgid "-b\t\t\tBinary mode" #~ msgstr "-b\t\t\tBinre modus" -#~ msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -#~ msgstr "-Z\t\t\tBeperkte modus (soos \"rvim\")" - #~ msgid "-R\t\t\tReadonly mode (like \"view\")" #~ msgstr "-R\t\t\tLeesalleen modus (soos \"view\")" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index be4206f36e..6c4d6ddd22 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -3469,10 +3469,6 @@ msgstr "-y\t\t\tMode senzill (com \"evim\", sense modes)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tMode noms lectura (com \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tMode restringit (com \"rvim)" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tNo permet modificar (escriure) fitxers" diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index 5b9f3d3a58..859039eb87 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -3547,10 +3547,6 @@ msgstr "-v\t\t\tSnadn reim (stejn jako \"evim\", dn mdy )" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tReim pouze_pro_ten (jako \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tOmezen reim (stejn jako \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tZmny (ukldn soubor) zakzny" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index 31a90dc514..4d9ad58836 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -3547,10 +3547,6 @@ msgstr "-v\t\t\tSnadn reim (stejn jako \"evim\", dn mdy )" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tReim pouze_pro_ten (jako \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tOmezen reim (stejn jako \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tZmny (ukldn soubor) zakzny" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index f35272810b..7a75425019 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -3015,9 +3015,6 @@ msgstr "-y\t\t\tEasy-tilstand (ligesom \"evim\", tilstandsløs)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tSkrivebeskyttet tilstand (ligesom \"view\")" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tRestriktiv tilstand (ligesom \"rvim\")" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tÆndringer (skrivning af filer) ikke tilladt" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index a2e04965e5..740e9e5f6a 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -2895,10 +2895,6 @@ msgstr "-y\t\t\tLeichter Modus (wie \"evim\", ohne Modi)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tModus ohne Schreibrechte (wie \"view\")" -#: ../main.c:2186 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tEingeschrnkter Modus (wie \"rvim\")" - #: ../main.c:2187 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tnderungen (beim Schreiben von Dateien) sind nicht erlaubt" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 7919fc8946..66cdba6f92 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -3368,10 +3368,6 @@ msgstr "" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "" diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 99c46c7275..5480e6a4d8 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -2989,9 +2989,6 @@ msgstr "-y\t\t\tFacila reĝimo (kiel \"evim\", senreĝima)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tNurlegebla reĝimo (kiel \"view\")" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tLimigita reĝimo (kiel \"rvim\")" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tŜanĝoj (skribo al dosieroj) nepermeseblaj" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index eeea27610d..064484d1a4 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -3524,10 +3524,6 @@ msgstr "-y\t\t\tModo fácil (como \"evim\", sin modo)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tModo de solo lectura (como \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tModo restringido (como \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tModificación de archivos desactivada" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index 4489139cfb..5986a57488 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -3249,10 +3249,6 @@ msgstr "" #~ msgstr " kahta tilaa varten " #, fuzzy -#~ msgid " -Z Restricted mode\n" -#~ msgstr " kahta tilaa varten " - -#, fuzzy #~ msgid " -m Modifications (writing files) not allowed\n" #~ msgstr "-m\t\t\tMuokkaukset (kirjoittaminen tiedostoon) pois käytöstä" @@ -6961,9 +6957,6 @@ msgstr "Lista tai luku tarvitaan" #~ msgid "-b\t\t\tBinary mode" #~ msgstr "-b\t\t\tBinääritila" -#~ msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -#~ msgstr "-Z\t\t\tRajoitettu tila (kuten rvimillä)" - #~ msgid "-R\t\t\tReadonly mode (like \"view\")" #~ msgstr "-R\t\t\tKirjoitussuojattu tila (kuten view'lla)" diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index bb60649c91..5f1ca2fec5 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -3231,9 +3231,6 @@ msgstr "-y\t\tMode facile (comme \"evim\", vim sans modes)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\tMode lecture seule (comme \"view\")" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\tMode restreint (comme \"rvim\")" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\tInterdire l'enregistrement des fichiers" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 1104b31c32..bad01d592a 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -3022,9 +3022,6 @@ msgstr "-y\t\t\tMd asca (mar \"evim\", gan mhid)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tMd inlite amhin (mar \"view\")" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tMd srianta (mar \"rvim\")" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tN cheadatear athruithe (.i. scrobh na gcomhad)" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 511f910b71..dfabc4bee0 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -3510,10 +3510,6 @@ msgstr "-y\t\t\tModalit Facile (come \"evim\", senza modalit)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tModalit Sola Lettura (come \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tModalit Ristretta (come \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tRiscritture del file non permesse" diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index 523e9ca4e3..e2cf68f016 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -3024,9 +3024,6 @@ msgstr "-y\t\t\t⡼ (\"evim\" Ʊ⡼̵)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tɹѥ⡼ (\"view\" Ʊ)" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\t¥⡼ (\"rvim\" Ʊ)" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tѹ (ե¸) Ǥʤ褦ˤ" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index 5a69d0c5bf..85a45cd171 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -3024,9 +3024,6 @@ msgstr "-y\t\t\tイージーモード (\"evim\" と同じ、モード無)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\t読込専用モード (\"view\" と同じ)" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\t制限モード (\"rvim\" と同じ)" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\t変更 (ファイル保存時) をできないようにする" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index 128b238f8b..b99c22caeb 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -3438,10 +3438,6 @@ msgstr "-y\t\t\t쉬운 상태 (\"evim\"과 같음, modeless)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\t읽기 전용 상태 (\"view\"와 같음)" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\t제한된 상태 (\"rvim\"과 같음)" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\t수정(파일 쓰기)이 허용되지 않음" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index 34617ccf18..2285d755cf 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -3452,10 +3452,6 @@ msgstr "-y\t\t\tLett modus (som \"evim\", uten modus)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tSkrivebeskyttet modus (som \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tBegrenset modus (som \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tModifisering (lagring av filer) ikke tillatt" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 30f34508f5..00d113c83c 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -3449,10 +3449,6 @@ msgstr "-y\t\t\tEenvoudige modus (zoals \"evim\", zonder modus)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tAlleen-lezen modus (zoals \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tBeperkte modus (zoals \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tAanpassingen (bestanden opslaan) niet toegestaan" diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index 34617ccf18..2285d755cf 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -3452,10 +3452,6 @@ msgstr "-y\t\t\tLett modus (som \"evim\", uten modus)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tSkrivebeskyttet modus (som \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tBegrenset modus (som \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tModifisering (lagring av filer) ikke tillatt" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index f5c452e924..5f1779d1bd 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -3417,10 +3417,6 @@ msgstr "-y\t\t\tTryb łatwy (jak \"evim\", bez trybów)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tTryb wyłącznie do odczytu (jak \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tTryb ograniczenia (jak \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tModyfikacje (zapisywanie plików) niedozwolone" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index 4f39cb5bdb..533d916de1 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -6229,10 +6229,6 @@ msgstr "-y\t\t\tModo fácil (como \"evim\", o Vim não modal)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tmodo somente-leitura (como \"view\")" -#: ../main.c:2186 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tmodo restrito (como \"rvim\")" - #: ../main.c:2187 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tNo permitir alteraes (gravao de arquivos)" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 62f892d257..3a96ece2fb 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -3442,10 +3442,6 @@ msgstr "-y\t\t\tПростой режим (как \"evim\", безрежимны msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tТолько для чтения (как \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tОграниченный режим (как \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tБез возможности сохранения изменений (записи файлов)" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index ced343bf6b..ff95c68a12 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -3450,10 +3450,6 @@ msgstr "-y\t\t\tJednoduch md (rovnak ako \"evim\", bezmdov)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tMd iba pre tanie (ako \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tObmedzen md (rovnak ako \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tZmeny (ukladanie sborov) zakzan" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index 66b3d5abb1..d35622726f 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -3450,10 +3450,6 @@ msgstr "-y\t\t\tJednoduch md (rovnak ako \"evim\", bezmdov)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tMd iba pre tanie (ako \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tObmedzen md (rovnak ako \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tZmeny (ukladanie sborov) zakzan" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index 4c157658e5..a93a2ec584 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -3028,9 +3028,6 @@ msgstr "-y\t\t\tEasy режим (као \"evim\", безрежимни)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tReadonly режим (као \"view\")" -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tRestricted режим (као \"rvim\")" - msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tИзмене (уписивање датотека) нису дозвољене" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index db7bada888..d50c9d695d 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -5735,10 +5735,6 @@ msgstr "-y\t\t\tLtt lge (som \"evim\", lgesls)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tSkrivskyddat lge (som \"view\")" -#: ../main.c:2186 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tBegrnsat lge (som \"rvim\")" - #: ../main.c:2187 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tModifieringar (skriva filer) inte tilltet" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 604e425bd0..f0ae154648 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: vim 7.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-23 18:45+0300\n" +"POT-Creation-Date: 2021-01-18 17:46+0200\n" "PO-Revision-Date: 2020-08-23 20:19+0300\n" "Last-Translator: Анатолій Сахнік <sakhnik@gmail.com>\n" "Language-Team: Ukrainian\n" @@ -23,6 +23,67 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +msgid "--Deleted--" +msgstr "--Знищено--" + +#, c-format +msgid "auto-removing autocommand: %s <buffer=%d>" +msgstr "Автоматичне знищення автокоманди: %s <буфер=%d>" + +#, c-format +msgid "E367: No such group: \"%s\"" +msgstr "E367: Немає такої групи: «%s»" + +msgid "E936: Cannot delete the current group" +msgstr "E936: Не вдалося знищити цю групу" + +msgid "W19: Deleting augroup that is still in use" +msgstr "W19: Знищення автогрупи, яка все ще використовується" + +#, c-format +msgid "E215: Illegal character after *: %s" +msgstr "E215: Недозволений символ після *: %s" + +#, c-format +msgid "E216: No such event: %s" +msgstr "E216: Немає такої події: %s" + +#, c-format +msgid "E216: No such group or event: %s" +msgstr "E216: Немає такої групи чи події: %s" + +msgid "" +"\n" +"--- Autocommands ---" +msgstr "" +"\n" +"--- Автокоманди ---" + +#, c-format +msgid "E680: <buffer=%d>: invalid buffer number " +msgstr "E680: <буфер=%d>: некоректний номер буфера " + +msgid "E217: Can't execute autocommands for ALL events" +msgstr "E217: Не можу виконувати автокоманди для УСІХ подій" + +msgid "No matching autocommands" +msgstr "Немає відповідних автокоманд" + +msgid "E218: autocommand nesting too deep" +msgstr "E218: Забагато вкладених автокоманд" + +#, c-format +msgid "%s Autocommands for \"%s\"" +msgstr "%s Автокоманди для «%s»" + +#, c-format +msgid "Executing %s" +msgstr "Виконується %s" + +#, c-format +msgid "autocommand %s" +msgstr "автокоманда %s" + msgid "[Location List]" msgstr "[Список місць]" @@ -95,9 +156,15 @@ msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: Буфер %<PRId64> має зміни (! щоб не зважати)" +msgid "E948: Job still running (add ! to end the job)" +msgstr "E948: Задача все ще виконується (! щоб закінчити)" + msgid "E37: No write since last change (add ! to override)" msgstr "E37: Зміни не було записано (! щоб не зважати)" +msgid "E948: Job still running" +msgstr "E948: Задача все ще виконується" + msgid "E37: No write since last change" msgstr "E37: Не записано після останніх змін" @@ -465,6 +532,10 @@ msgid "E957: Invalid window number" msgstr "E957: Некоректний номер вікна" #, c-format +msgid "E940: Cannot lock or unlock variable %s" +msgstr "E940: Неможливо заблокувати чи розблокувати змінну %s" + +#, c-format msgid "E734: Wrong variable type for %s=" msgstr "E734: Неправильний тип змінної для %s=" @@ -544,10 +615,6 @@ msgstr "E690: Пропущено «in» після :for" msgid "E108: No such variable: \"%s\"" msgstr "E108: Змінної немає: «%s»" -#, c-format -msgid "E940: Cannot lock or unlock variable %s" -msgstr "E940: Неможливо заблокувати чи розблокувати змінну %s" - msgid "E109: Missing ':' after '?'" msgstr "E109: Бракує ':' після '?'" @@ -950,9 +1017,6 @@ msgstr "E684: Індекс списку поза межами: %<PRId64>" msgid "E686: Argument of %s must be a List" msgstr "E686: Аргумент у %s має бути списком" -msgid "E928: String required" -msgstr "E928: Потрібен String" - #, c-format msgid "Error converting the call result: %s" msgstr "Не вдалося перетворити результат виклику: %s" @@ -1591,6 +1655,10 @@ msgid "Close \"%s\"?" msgstr "Закрити «%s»?" #, c-format +msgid "E947: Job still running in buffer \"%s\"" +msgstr "E947: Задача все ще запущена у буфері «%s»" + +#, c-format msgid "E162: No write since last change for buffer \"%s\"" msgstr "E162: Буфер «%s» має незбережені зміни" @@ -1615,18 +1683,6 @@ msgid "E666: compiler not supported: %s" msgstr "E666: Компілятор не підтримується: %s" #, c-format -msgid "Searching for \"%s\" in \"%s\"" -msgstr "Пошук «%s» в «%s»" - -#, c-format -msgid "Searching for \"%s\"" -msgstr "Пошук «%s»" - -#, c-format -msgid "not found in '%s': \"%s\"" -msgstr "не знайдено в '%s': «%s»" - -#, c-format msgid ":source error parsing command %s" msgstr ":source помилка розбору команди %s" @@ -1702,12 +1758,16 @@ msgstr "Режим Ex. Для повернення до нормального msgid "E501: At end-of-file" msgstr "E501: Кінець файлу" -msgid "E169: Command too recursive" -msgstr "E169: Команда занадто рекурсивна" +#, c-format +msgid "Executing: %s" +msgstr "Виконується: %s" msgid "line %" msgstr "рядок %" +msgid "E169: Command too recursive" +msgstr "E169: Команда занадто рекурсивна" + #, c-format msgid "E605: Exception not caught: %s" msgstr "E605: Виняткова ситуація не оброблена: %s" @@ -1724,9 +1784,6 @@ msgstr "E464: Неоднозначний вжиток команди корис msgid "E492: Not an editor command" msgstr "E492: Це не команда редактора" -msgid "E981: Command not allowed in restricted mode" -msgstr "E981: Команду не дозволено у обмеженому режимі" - msgid "E493: Backwards range given" msgstr "E493: Інтервал задано навиворіт" @@ -1736,6 +1793,11 @@ msgstr "Інтервал задано навиворіт, щоб помінят msgid "E494: Use w or w>>" msgstr "E494: Спробуйте w або w>>" +msgid "" +"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" +msgstr "" +"ВНУТРІШНЄ: Не можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи ADDR_QUICKFIX" + msgid "E943: Command table needs to be updated, run 'make'" msgstr "E943: Потрібно поновити таблицю команд, запустіть 'make'" @@ -1762,10 +1824,10 @@ msgstr "E174: Команда вже існує, ! щоб замінити її: msgid "" "\n" -" Name Args Address Complete Definition" +" Name Args Address Complete Definition" msgstr "" "\n" -" Назва Арг. Адреса Доповнення Визначення" +" Назва Арг. Адреса Доповнення Визначення" msgid "No user-defined commands found" msgstr "Не знайдено команд користувача" @@ -2077,12 +2139,12 @@ msgstr "E347: У шляху пошуку більше немає файлів « msgid "E812: Autocommands changed buffer or buffer name" msgstr "E812: Автокоманди змінили буфер чи його назву" -msgid "Illegal file name" -msgstr "Недозволена назва файлу" - msgid "is a directory" msgstr "каталог" +msgid "Illegal file name" +msgstr "Недозволена назва файлу" + msgid "is not a file" msgstr "не файл" @@ -2104,9 +2166,6 @@ msgstr "E201: Автокоманди *ReadPre не повинні змінюва msgid "E202: Conversion made file unreadable!" msgstr "E202: Конвертація унеможливила читання файлу!" -msgid "[fifo/socket]" -msgstr "[канал/сокет]" - msgid "[fifo]" msgstr "[канал]" @@ -2353,67 +2412,6 @@ msgstr "E462: Не вдалося підготувати «%s», щоб пере msgid "E321: Could not reload \"%s\"" msgstr "E321: Не вдалося перечитати «%s»" -msgid "--Deleted--" -msgstr "--Знищено--" - -#, c-format -msgid "auto-removing autocommand: %s <buffer=%d>" -msgstr "Автоматичне знищення автокоманди: %s <буфер=%d>" - -#, c-format -msgid "E367: No such group: \"%s\"" -msgstr "E367: Немає такої групи: «%s»" - -msgid "E936: Cannot delete the current group" -msgstr "E936: Не вдалося знищити цю групу" - -msgid "W19: Deleting augroup that is still in use" -msgstr "W19: Знищення автогрупи, яка все ще використовується" - -#, c-format -msgid "E215: Illegal character after *: %s" -msgstr "E215: Недозволений символ після *: %s" - -#, c-format -msgid "E216: No such event: %s" -msgstr "E216: Немає такої події: %s" - -#, c-format -msgid "E216: No such group or event: %s" -msgstr "E216: Немає такої групи чи події: %s" - -msgid "" -"\n" -"--- Autocommands ---" -msgstr "" -"\n" -"--- Автокоманди ---" - -#, c-format -msgid "E680: <buffer=%d>: invalid buffer number " -msgstr "E680: <буфер=%d>: некоректний номер буфера " - -msgid "E217: Can't execute autocommands for ALL events" -msgstr "E217: Не можу виконувати автокоманди для УСІХ подій" - -msgid "No matching autocommands" -msgstr "Немає відповідних автокоманд" - -msgid "E218: autocommand nesting too deep" -msgstr "E218: Забагато вкладених автокоманд" - -#, c-format -msgid "%s Autocommands for \"%s\"" -msgstr "%s Автокоманди для «%s»" - -#, c-format -msgid "Executing %s" -msgstr "Виконується %s" - -#, c-format -msgid "autocommand %s" -msgstr "автокоманда %s" - msgid "E219: Missing {." msgstr "E219: Бракує {." @@ -2519,9 +2517,6 @@ msgstr "E685: Внутрішня помилка: %s" msgid "Interrupted" msgstr "Перервано" -msgid "E14: Invalid address" -msgstr "E14: Неправильна адреса" - msgid "E474: Invalid argument" msgstr "E474: Некоректний аргумент" @@ -2719,6 +2714,9 @@ msgstr "E45: Встановлено опцію 'readonly' (! щоб не зва msgid "E46: Cannot change read-only variable \"%.*s\"" msgstr "E46: Змінна тільки для читання: «%.*s»" +msgid "E928: String required" +msgstr "E928: Потрібен String" + msgid "E715: Dictionary required" msgstr "E715: Потрібен словник" @@ -2727,8 +2725,8 @@ msgid "E118: Too many arguments for function: %s" msgstr "E118: Забагато аргументів для функції: %s" #, c-format -msgid "E716: Key not present in Dictionary: %s" -msgstr "E716: Немає такого ключа у словнику: %s" +msgid "E716: Key not present in Dictionary: \"%s\"" +msgstr "E716: Немає такого ключа у словнику: «%s»" msgid "E714: List required" msgstr "E714: Потрібен список" @@ -3158,6 +3156,10 @@ msgid "E5106: Error while creating shared module: %.*s" msgstr "E5106: Помилка створення розділюваного модуля: %.*s" #, c-format +msgid "E5106: Error while creating inspect module: %.*s" +msgstr "E5106: Помилка створення модуля inspect: %.*s" + +#, c-format msgid "E5106: Error while creating vim module: %.*s" msgstr "E5106: Помилка створення модуля vim: %.*s" @@ -3165,10 +3167,6 @@ msgid "E970: Failed to initialize lua interpreter" msgstr "E970: Не вдалося ініціалізувати інтерпретатор lua" #, c-format -msgid "E5117: Error while updating package paths: %.*s" -msgstr "E5117: Помилка оновлення шляхів пакунку: %.*s" - -#, c-format msgid "E5114: Error while converting print argument #%i: %.*s" msgstr "E5114: Не вдалося перетворити аргумент #%i друку: %.*s" @@ -3256,6 +3254,10 @@ msgid "pre-vimrc command line" msgstr "команди перед vimrc" #, c-format +msgid "Conflicting configs: \"%s\" \"%s\"" +msgstr "Суперечливі конфігурації: «%s» «%s»" + +#, c-format msgid "E282: Cannot read from \"%s\"" msgstr "E282: Не вдалося прочитати з «%s»" @@ -3367,9 +3369,6 @@ msgstr " -v, --version Надрукувати інформацію пр msgid " -V[N][file] Verbose [level][file]\n" msgstr " -V[N][файл] Більше повідомлень [рівень][файл]\n" -msgid " -Z Restricted mode\n" -msgstr " -Z Обмежений режим\n" - msgid " --api-info Write msgpack-encoded API metadata to stdout\n" msgstr "" " --api-info Записати метадані API, серіалізовані у msgpack, у " @@ -4005,8 +4004,7 @@ msgstr "" "Введіть :qa! і натисність <Enter> щоб відкинути всі зміни і вийти Nvim" msgid "Type :qa and press <Enter> to exit Nvim" -msgstr "" -"Введіть :qa і натисність <Enter> щоб вийти з Nvim" +msgstr "Введіть :qa і натисність <Enter> щоб вийти з Nvim" #, c-format msgid "1 line %sed 1 time" @@ -4071,10 +4069,10 @@ msgstr "E353: У регістрі %s нічого немає" msgid "" "\n" -"--- Registers ---" +"Type Name Content" msgstr "" "\n" -"--- Регістри ---" +"Тип Наз. Вміст " msgid "" "E883: search pattern and expression register may not contain two or more " @@ -4187,9 +4185,6 @@ msgstr "E537: 'commentstring' має бути порожньою чи місти msgid "E540: Unclosed expression sequence" msgstr "E540: Послідовність виразів не завершено" -msgid "E541: too many items" -msgstr "E541: Забагато елементів" - msgid "E542: unbalanced groups" msgstr "E542: Групи не збалансовано" @@ -4277,6 +4272,9 @@ msgstr "" msgid "E5677: Error writing input to shell-command: %s" msgstr "E5677: Не вдалося записати на вхід команди оболонки: %s" +msgid "%a %b %d %H:%M:%S %Y" +msgstr "%H:%M:%S %a, %d %B %Y р." + #, c-format msgid "E447: Can't find file \"%s\" in path" msgstr "E447: Файл «%s» не знайдено у шляху пошуку" @@ -4284,8 +4282,11 @@ msgstr "E447: Файл «%s» не знайдено у шляху пошуку" msgid "E553: No more items" msgstr "E553: Немає більше елементів" +msgid "E925: Current quickfix list was changed" +msgstr "E925: Поточний список quickfix змінився" + msgid "E926: Current location list was changed" -msgstr "E926: Цей список місць було змінено" +msgstr "E926: Поточний список місць змінився" #, c-format msgid "E372: Too many %%%c in format string" @@ -4319,9 +4320,6 @@ msgstr "E379: Пропущена чи порожня назва каталогу msgid "E924: Current window was closed" msgstr "E924: Активне вікно було закрито" -msgid "E925: Current quickfix was changed" -msgstr "E925: Цей quickfix було змінено" - #, c-format msgid "(%d of %d)%s%s: " msgstr "(%d з %d)%s%s: " @@ -4349,6 +4347,9 @@ msgstr "E683: Пропущено назву файлу чи некоректни msgid "Cannot open file \"%s\"" msgstr "Не вдалося відкрити файл «%s»" +msgid "cannot have both a list and a \"what\" argument" +msgstr "не можна задавати одночасно список і аргумент «що»" + msgid "E681: Buffer is not loaded" msgstr "E681: Буфер не завантажено" @@ -4469,6 +4470,18 @@ msgstr "" msgid "Switching to backtracking RE engine for pattern: " msgstr "Перемикання до простого рушія регулярних виразів: " +#, c-format +msgid "Searching for \"%s\" in \"%s\"" +msgstr "Пошук «%s» в «%s»" + +#, c-format +msgid "Searching for \"%s\"" +msgstr "Пошук «%s»" + +#, c-format +msgid "not found in '%s': \"%s\"" +msgstr "не знайдено в '%s': «%s»" + msgid " TERMINAL" msgstr " ТЕРМІНАЛ" @@ -5252,6 +5265,9 @@ msgstr "синхронізується по коментарях стилю С" msgid "no syncing" msgstr "без синхронізації" +msgid "syncing starts at the first line" +msgstr "синхронізація починається з першого рядка" + msgid "syncing starts " msgstr "починається синхронізація за " @@ -5283,6 +5299,9 @@ msgstr "" msgid "E392: No such syntax cluster: %s" msgstr "E392: Немає такого синтаксичного кластера: %s" +msgid "from the first line" +msgstr "з першого рядка" + msgid "minimal " msgstr "мінімальний " diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index a954ea6e34..c693f910d8 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -3479,10 +3479,6 @@ msgstr "-y\t\t\tChế độ đơn giản (giống \"evim\", không có chế đ msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\tChế độ chỉ đọc (giống \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\tChế độ hạn chế (giống \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\tKhông có khả năng ghi nhớ thay đổi (ghi nhớ tập tin)" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 542157002a..1e329443ce 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -3427,10 +3427,6 @@ msgstr "-y\t\t\t容易模式 (同 \"evim\",无模式)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\t只读模式 (同 \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\t限制模式 (同 \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\t不可修改(写入文件)" diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index 6a11b5e669..c97f31ddcf 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -3482,10 +3482,6 @@ msgstr "-y\t\t\t簡易模式 (同 \"evim\", modeless)" msgid "-R\t\t\tReadonly mode (like \"view\")" msgstr "-R\t\t\t唯讀模式 (同 \"view\")" -#: ../main.c:2208 -msgid "-Z\t\t\tRestricted mode (like \"rvim\")" -msgstr "-Z\t\t\t限制模式 (同 \"rvim\")" - #: ../main.c:2209 msgid "-m\t\t\tModifications (writing files) not allowed" msgstr "-m\t\t\t不可修改 (寫入檔案)" diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a625c09f78..e074f73d04 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5264,7 +5264,7 @@ void ex_vimgrep(exarg_T *eap) qf_new_list(qi, title); } - // parse the list of arguments + // Parse the list of arguments, wildcards have already been expanded. if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) { goto theend; } @@ -5648,7 +5648,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) == FAIL)) { // tv_dict_add* fail only if key already exist, but this is a newly // allocated dictionary which is thus guaranteed to have no existing keys. - assert(false); + abort(); } return OK; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a7fd2bfcc6..a78f905a70 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2417,8 +2417,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (wp->w_p_list) { - if (curwin->w_p_lcs_chars.space + if (wp->w_p_list && !has_fold) { + if (wp->w_p_lcs_chars.space || wp->w_p_lcs_chars.trail || wp->w_p_lcs_chars.nbsp) { extra_check = true; @@ -2665,7 +2665,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - //sign column + // sign column, this is hit until sign_idx reaches count if (draw_state == WL_SIGN - 1 && n_extra == 0) { draw_state = WL_SIGN; /* Show the sign column when there are any signs in this @@ -2883,8 +2883,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (draw_state == WL_LINE - && foldinfo.fi_level != 0 - && foldinfo.fi_lines > 0 + && has_fold && vcol == 0 && n_extra == 0 && row == startrow) { @@ -2905,8 +2904,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (draw_state == WL_LINE - && foldinfo.fi_level != 0 - && foldinfo.fi_lines > 0 + && has_fold && col < grid->Columns && n_extra == 0 && row == startrow) { @@ -2918,8 +2916,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (draw_state == WL_LINE - && foldinfo.fi_level != 0 - && foldinfo.fi_lines > 0 + && has_fold && col >= grid->Columns && n_extra != 0 && row == startrow) { @@ -3087,7 +3084,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, || vcol < fromcol || vcol_prev < fromcol_prev || vcol >= tocol)) { char_attr = line_attr; - } else { + } else { attr_pri = false; if (has_syntax) { char_attr = syntax_attr; @@ -3742,7 +3739,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } wp->w_wrow = row; did_wcol = true; - curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; + wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } // Don't override visual selection highlighting. @@ -3835,9 +3832,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // Add a blank character to highlight. schar_from_ascii(linebuf_char[off], ' '); } - if (area_attr == 0) { - /* Use attributes from match with highest priority among - * 'search_hl' and the match list. */ + if (area_attr == 0 && !has_fold) { + // Use attributes from match with highest priority among + // 'search_hl' and the match list. char_attr = search_hl.attr; cur = wp->w_match_head; shl_flag = FALSE; @@ -4053,6 +4050,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && !wp->w_p_wrap && filler_todo <= 0 && (wp->w_p_rl ? col == 0 : col == grid->Columns - 1) + && !has_fold && (*ptr != NUL || lcs_eol_one > 0 || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { @@ -4347,6 +4345,10 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) // Get information needed to display the sign in line 'lnum' in window 'wp'. // If 'nrcol' is TRUE, the sign is going to be displayed in the number column. // Otherwise the sign is going to be displayed in the sign column. +// +// @param count max number of signs +// @param[out] n_extrap number of characters from pp_extra to display +// @param[in, out] sign_idxp Index of the displayed sign static void get_sign_display_info( bool nrcol, win_T *wp, @@ -4423,6 +4425,8 @@ static void get_sign_display_info( (*sign_idxp)++; if (*sign_idxp < count) { *draw_statep = WL_SIGN - 1; + } else { + *sign_idxp = 0; } } diff --git a/src/nvim/search.c b/src/nvim/search.c index 787a464070..2802da6f7f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4497,9 +4497,9 @@ find_pattern_in_path( regmatch_T regmatch; regmatch_T incl_regmatch; regmatch_T def_regmatch; - int matched = FALSE; - int did_show = FALSE; - int found = FALSE; + bool matched = false; + bool did_show = false; + bool found = false; int i; char_u *already = NULL; char_u *startp = NULL; @@ -4611,7 +4611,7 @@ find_pattern_in_path( } MSG_PUTS_TITLE(_("in path ---\n")); } - did_show = TRUE; + did_show = true; while (depth_displayed < depth && !got_int) { ++depth_displayed; for (i = 0; i < depth_displayed; i++) @@ -4761,10 +4761,10 @@ search_line: matched = !STRNCMP(startp, ptr, len); if (matched && define_matched && whole && vim_iswordc(startp[len])) - matched = FALSE; + matched = false; } else if (regmatch.regprog != NULL && vim_regexec(®match, line, (colnr_T)(p - line))) { - matched = TRUE; + matched = true; startp = regmatch.startp[0]; // Check if the line is not a comment line (unless we are // looking for a define). A line starting with "# define" @@ -4789,15 +4789,16 @@ search_line: if (matched && p[0] == '/' && (p[1] == '*' || p[1] == '/')) { - matched = FALSE; - /* After "//" all text is comment */ - if (p[1] == '/') + matched = false; + // After "//" all text is comment + if (p[1] == '/') { break; - ++p; + } + p++; } else if (!matched && p[0] == '*' && p[1] == '/') { - /* Can find match after "* /". */ - matched = TRUE; - ++p; + // Can find match after "* /". + matched = true; + p++; } } } @@ -4811,7 +4812,7 @@ search_line: if (depth == -1 && lnum == curwin->w_cursor.lnum) break; - found = TRUE; + found = true; aux = p = startp; if (compl_cont_status & CONT_ADDING) { p += compl_length; @@ -4879,9 +4880,10 @@ search_line: break; } } else if (action == ACTION_SHOW_ALL) { - found = TRUE; - if (!did_show) - gotocmdline(TRUE); /* cursor at status line */ + found = true; + if (!did_show) { + gotocmdline(true); // cursor at status line + } if (curr_fname != prev_fname) { if (did_show) msg_putchar('\n'); /* cursor below last one */ @@ -4890,28 +4892,28 @@ search_line: msg_home_replace_hl(curr_fname); prev_fname = curr_fname; } - did_show = TRUE; - if (!got_int) - show_pat_in_path(line, type, TRUE, action, - (depth == -1) ? NULL : files[depth].fp, - (depth == -1) ? &lnum : &files[depth].lnum, - match_count++); + did_show = true; + if (!got_int) { + show_pat_in_path(line, type, true, action, + (depth == -1) ? NULL : files[depth].fp, + (depth == -1) ? &lnum : &files[depth].lnum, + match_count++); + } /* Set matched flag for this file and all the ones that * include it */ for (i = 0; i <= depth; ++i) files[i].matched = TRUE; } else if (--count <= 0) { - found = TRUE; + found = true; if (depth == -1 && lnum == curwin->w_cursor.lnum - && l_g_do_tagpreview == 0 - ) + && l_g_do_tagpreview == 0) { EMSG(_("E387: Match is on current line")); - else if (action == ACTION_SHOW) { + } else if (action == ACTION_SHOW) { show_pat_in_path(line, type, did_show, action, - (depth == -1) ? NULL : files[depth].fp, - (depth == -1) ? &lnum : &files[depth].lnum, 1L); - did_show = TRUE; + (depth == -1) ? NULL : files[depth].fp, + (depth == -1) ? &lnum : &files[depth].lnum, 1L); + did_show = true; } else { /* ":psearch" uses the preview window */ if (l_g_do_tagpreview != 0) { @@ -4960,15 +4962,16 @@ search_line: break; } exit_matched: - matched = FALSE; - /* look for other matches in the rest of the line if we - * are not at the end of it already */ + matched = false; + // look for other matches in the rest of the line if we + // are not at the end of it already if (def_regmatch.regprog == NULL && action == ACTION_EXPAND && !(compl_cont_status & CONT_SOL) && *startp != NUL - && *(p = startp + utfc_ptr2len(startp)) != NUL) + && *(p = startp + utfc_ptr2len(startp)) != NUL) { goto search_line; + } } line_breakcheck(); if (action == ACTION_EXPAND) @@ -5046,16 +5049,20 @@ fpip_end: vim_regfree(def_regmatch.regprog); } -static void show_pat_in_path(char_u *line, int type, int did_show, int action, FILE *fp, linenr_T *lnum, long count) +static void show_pat_in_path(char_u *line, int type, bool did_show, int action, + FILE *fp, linenr_T *lnum, long count) + FUNC_ATTR_NONNULL_ARG(1, 6) { char_u *p; - if (did_show) - msg_putchar('\n'); /* cursor below last one */ - else if (!msg_silent) - gotocmdline(TRUE); /* cursor at status line */ - if (got_int) /* 'q' typed at "--more--" message */ + if (did_show) { + msg_putchar('\n'); // cursor below last one + } else if (!msg_silent) { + gotocmdline(true); // cursor at status line + } + if (got_int) { // 'q' typed at "--more--" message return; + } for (;; ) { p = line + STRLEN(line) - 1; if (fp != NULL) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2444910bb3..c0e787380f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -765,7 +765,7 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader, (uint64_t) offset); return kSDReadStatusNotShaDa; } - assert(false); + abort(); } return kSDReadStatusSuccess; } @@ -1224,7 +1224,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDReadStatusFinished: { // Should be handled by the while condition. - assert(false); + abort(); } case kSDReadStatusNotShaDa: case kSDReadStatusReadError: { @@ -1236,7 +1236,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } switch (cur_entry.type) { case kSDItemMissing: { - assert(false); + abort(); } case kSDItemUnknown: { break; @@ -1628,7 +1628,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ((size_t) (!CHECK_DEFAULT(entry, attr))) switch (entry.type) { case kSDItemMissing: { - assert(false); + abort(); } case kSDItemUnknown: { if (spacker->callback(spacker->data, entry.data.unknown_item.contents, @@ -1850,7 +1850,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } default: { - assert(false); + abort(); } } } @@ -2147,7 +2147,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDReadStatusFinished: { // Should be handled by the while condition. - assert(false); + abort(); } case kSDReadStatusNotShaDa: { ret = kSDWriteReadNotShada; @@ -2184,7 +2184,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDItemHeader: case kSDItemBufferList: { - assert(false); + abort(); } case kSDItemUnknown: { ret = shada_pack_entry(packer, entry, 0); @@ -4044,7 +4044,7 @@ shada_read_next_item_start: } case kSDItemMissing: case kSDItemUnknown: { - assert(false); + abort(); } } entry->type = (ShadaEntryType) type_u64; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 5714f5e425..6425c9fed5 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3123,6 +3123,7 @@ spell_find_suggest ( static bool expr_busy = false; int c; langp_T *lp; + bool did_intern = false; // Set the info in "*su". memset(su, 0, sizeof(suginfo_T)); @@ -3206,14 +3207,16 @@ spell_find_suggest ( spell_suggest_expr(su, buf + 5); expr_busy = false; } - } else if (STRNCMP(buf, "file:", 5) == 0) + } else if (STRNCMP(buf, "file:", 5) == 0) { // Use list of suggestions in a file. spell_suggest_file(su, buf + 5); - else { - // Use internal method. + } else if (!did_intern) { + // Use internal method once. spell_suggest_intern(su, interactive); - if (sps_flags & SPS_DOUBLE) + if (sps_flags & SPS_DOUBLE) { do_combine = true; + } + did_intern = true; } } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 90af010164..3c125959a9 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5387,7 +5387,8 @@ spell_add_word ( len, word, NameBuff); } } - if (fseek(fd, fpos_next, SEEK_SET) <= 0) { + if (fseek(fd, fpos_next, SEEK_SET) != 0) { + PERROR(_("Seek error in spellfile")); break; } } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index c6b1a0d04c..84ca240734 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1461,7 +1461,7 @@ find_tags( p_ic = ignorecase_opt(pat, true, true); break; default: - assert(false); + abort(); } help_save = curbuf->b_help; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 39e2ca6171..642c443318 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -535,8 +535,44 @@ void terminal_send(Terminal *term, char *data, size_t size) term->opts.write_cb(data, size, term->opts.data); } +static bool is_filter_char(int c) +{ + unsigned int flag = 0; + switch (c) { + case 0x08: + flag = TPF_BS; + break; + case 0x09: + flag = TPF_HT; + break; + case 0x0A: + case 0x0D: + break; + case 0x0C: + flag = TPF_FF; + break; + case 0x1b: + flag = TPF_ESC; + break; + case 0x7F: + flag = TPF_DEL; + break; + default: + if (c < ' ') { + flag = TPF_C0; + } else if (c >= 0x80 && c <= 0x9F) { + flag = TPF_C1; + } + } + return !!(tpf_flags & flag); +} + void terminal_paste(long count, char_u **y_array, size_t y_size) { + vterm_keyboard_start_paste(curbuf->terminal->vt); + terminal_flush_output(curbuf->terminal); + size_t buff_len = STRLEN(y_array[0]); + char_u *buff = xmalloc(buff_len); for (int i = 0; i < count; i++) { // -V756 // feed the lines to the terminal for (size_t j = 0; j < y_size; j++) { @@ -544,9 +580,28 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) // terminate the previous line terminal_send(curbuf->terminal, "\n", 1); } - terminal_send(curbuf->terminal, (char *)y_array[j], STRLEN(y_array[j])); + size_t len = STRLEN(y_array[j]); + if (len > buff_len) { + buff = xrealloc(buff, len); + buff_len = len; + } + char_u *dst = buff; + char_u *src = y_array[j]; + while (*src != '\0') { + len = (size_t)utf_ptr2len(src); + int c = utf_ptr2char(src); + if (!is_filter_char(c)) { + memcpy(dst, src, len); + dst += len; + } + src += len; + } + terminal_send(curbuf->terminal, (char *)buff, (size_t)(dst - buff)); } } + xfree(buff); + vterm_keyboard_end_paste(curbuf->terminal->vt); + terminal_flush_output(curbuf->terminal); } void terminal_flush_output(Terminal *term) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 641e98ab30..1f3a45a9ab 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1110,14 +1110,14 @@ func Test_BufReadCmd() endfunc func SetChangeMarks(start, end) - exe a:start. 'mark [' - exe a:end. 'mark ]' + exe a:start .. 'mark [' + exe a:end .. 'mark ]' endfunc " Verify the effects of autocmds on '[ and '] func Test_change_mark_in_autocmds() edit! Xtest - call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u", 'xtn') + call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u\<Esc>", 'xtn') call SetChangeMarks(2, 3) write @@ -1279,26 +1279,9 @@ func Test_TextYankPost() bwipe! endfunc -func Test_nocatch_wipe_all_buffers() - " Real nasty autocommand: wipe all buffers on any event. - au * * bwipe * - call assert_fails('next x', 'E93') - bwipe - au! -endfunc - -func Test_nocatch_wipe_dummy_buffer() - " Nasty autocommand: wipe buffer on any event. - au * x bwipe - call assert_fails('lv½ /x', 'E480') - au! -endfunc - -func Test_wipe_cbuffer() - sv x - au * * bw - lb - au! +func Test_autocommand_all_events() + call assert_fails('au * * bwipe', 'E1155:') + call assert_fails('au * x bwipe', 'E1155:') endfunc " Test TextChangedI and TextChangedP @@ -1956,4 +1939,15 @@ func Test_autocmd_window() %bw! endfunc +func Test_autocmd_closes_window() + au BufNew,BufWinLeave * e %e + file yyy + au BufNew,BufWinLeave * ball + call assert_fails('n xxx', 'E143:') + + bwipe % + au! BufNew + au! BufWinLeave +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim new file mode 100644 index 0000000000..d680b442db --- /dev/null +++ b/src/nvim/testdir/test_backspace_opt.vim @@ -0,0 +1,151 @@ +" Tests for 'backspace' settings + +func Exec(expr) + let str='' + try + exec a:expr + catch /.*/ + let str=v:exception + endtry + return str +endfunc + +func Test_backspace_option() + set backspace= + call assert_equal('', &backspace) + set backspace=indent + call assert_equal('indent', &backspace) + set backspace=eol + call assert_equal('eol', &backspace) + set backspace=start + call assert_equal('start', &backspace) + set backspace=nostop + call assert_equal('nostop', &backspace) + " Add the value + set backspace= + set backspace=indent + call assert_equal('indent', &backspace) + set backspace+=eol + call assert_equal('indent,eol', &backspace) + set backspace+=start + call assert_equal('indent,eol,start', &backspace) + set backspace+=nostop + call assert_equal('indent,eol,start,nostop', &backspace) + " Delete the value + set backspace-=nostop + call assert_equal('indent,eol,start', &backspace) + set backspace-=indent + call assert_equal('eol,start', &backspace) + set backspace-=start + call assert_equal('eol', &backspace) + set backspace-=eol + call assert_equal('', &backspace) + " Check the error + call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474')) + call assert_equal(0, match(Exec('set backspace+=def'), '.*E474')) + " NOTE: Vim doesn't check following error... + "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) + + " Check backwards compatibility with version 5.4 and earlier + set backspace=0 + call assert_equal('0', &backspace) + set backspace=1 + call assert_equal('1', &backspace) + set backspace=2 + call assert_equal('2', &backspace) + set backspace=3 + call assert_equal('3', &backspace) + call assert_false(match(Exec('set backspace=4'), '.*E474')) + call assert_false(match(Exec('set backspace=10'), '.*E474')) + + " Cleared when 'compatible' is set + " set compatible + " call assert_equal('', &backspace) + set nocompatible viminfo+=nviminfo +endfunc + +" Test with backspace set to the non-compatible setting +func Test_backspace_ctrl_u() + new + call append(0, [ + \ "1 this shouldn't be deleted", + \ "2 this shouldn't be deleted", + \ "3 this shouldn't be deleted", + \ "4 this should be deleted", + \ "5 this shouldn't be deleted", + \ "6 this shouldn't be deleted", + \ "7 this shouldn't be deleted", + \ "8 this shouldn't be deleted (not touched yet)"]) + call cursor(2, 1) + + " set compatible + set backspace=2 + + exe "normal Avim1\<C-U>\<Esc>\<CR>" + exe "normal Avim2\<C-G>u\<C-U>\<Esc>\<CR>" + + set cpo-=< + inoremap <c-u> <left><c-u> + exe "normal Avim3\<C-U>\<Esc>\<CR>" + iunmap <c-u> + exe "normal Avim4\<C-U>\<C-U>\<Esc>\<CR>" + + " Test with backspace set to the compatible setting + set backspace= visualbell + exe "normal A vim5\<Esc>A\<C-U>\<C-U>\<Esc>\<CR>" + exe "normal A vim6\<Esc>Azwei\<C-G>u\<C-U>\<Esc>\<CR>" + + inoremap <c-u> <left><c-u> + exe "normal A vim7\<C-U>\<C-U>\<Esc>\<CR>" + + call assert_equal([ + \ "1 this shouldn't be deleted", + \ "2 this shouldn't be deleted", + \ "3 this shouldn't be deleted", + \ "4 this should be deleted3", + \ "", + \ "6 this shouldn't be deleted vim5", + \ "7 this shouldn't be deleted vim6", + \ "8 this shouldn't be deleted (not touched yet) vim7", + \ ""], getline(1, '$')) + + " Reset values + set compatible&vim + set visualbell&vim + set backspace&vim + + " Test new nostop option + %d_ + let expected = "foo bar foobar" + call setline(1, expected) + call cursor(1, 8) + exe ":norm! ianotherone\<c-u>" + call assert_equal(expected, getline(1)) + call cursor(1, 8) + exe ":norm! ianothertwo\<c-w>" + call assert_equal(expected, getline(1)) + + let content = getline(1) + for value in ['indent,nostop', 'eol,nostop', 'indent,eol,nostop', 'indent,eol,start,nostop'] + exe ":set bs=".. value + %d _ + call setline(1, content) + let expected = " foobar" + call cursor(1, 8) + exe ":norm! ianotherone\<c-u>" + call assert_equal(expected, getline(1), 'CTRL-U backspace value: '.. &bs) + let expected = "foo foobar" + call setline(1, content) + call cursor(1, 8) + exe ":norm! ianothertwo\<c-w>" + call assert_equal(expected, getline(1), 'CTRL-W backspace value: '.. &bs) + endfor + + " Reset options + set compatible&vim + set visualbell&vim + set backspace&vim + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 9101f8cfa0..d361205baa 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -37,17 +37,27 @@ func Test_compiler() bw! endfunc +func GetCompilerNames() + " return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) + " \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) + " \ ->sort() + return sort(map(glob('$VIMRUNTIME/compiler/*.vim', 0, 1), {i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})) +endfunc + func Test_compiler_without_arg() let runtime = substitute($VIMRUNTIME, '\\', '/', 'g') let a = split(execute('compiler')) - call assert_match(runtime .. '/compiler/ant.vim$', a[0]) - call assert_match(runtime .. '/compiler/bcc.vim$', a[1]) - call assert_match(runtime .. '/compiler/xo.vim$', a[-1]) + let exp = GetCompilerNames() + call assert_match(runtime .. '/compiler/' .. exp[0] .. '.vim$', a[0]) + call assert_match(runtime .. '/compiler/' .. exp[1] .. '.vim$', a[1]) + call assert_match(runtime .. '/compiler/' .. exp[-1] .. '.vim$', a[-1]) endfunc func Test_compiler_completion() + " let clist = GetCompilerNames()->join(' ') + let clist = join(GetCompilerNames(), ' ') call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"compiler ant bcc .* xmlwf xo$', @:) + call assert_match('^"compiler ' .. clist .. '$', @:) call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"compiler pbx perl php pylint pyunit', @:) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 061364fb73..ff50d53d86 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -22,6 +22,17 @@ func Test_E963() call assert_equal(v_o, v:oldfiles) endfunc +func Test_for_invalid() + call assert_fails("for x in 99", 'E714:') + call assert_fails("for x in 'asdf'", 'E714:') + call assert_fails("for x in {'a': 9}", 'E714:') + + if 0 + /1/5/2/s/\n + endif + redraw +endfunc + func Test_mkdir_p() call mkdir('Xmkdir/nested', 'p') call assert_true(isdirectory('Xmkdir/nested')) diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 1cb68e7fef..f9f0ade1f6 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -159,6 +159,7 @@ let s:filename_checks = { \ 'elinks': ['elinks.conf'], \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], + \ 'epuppet': ['file.epp'], \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], \ 'eruby': ['file.erb', 'file.rhtml'], \ 'esmtprc': ['anyesmtprc'], @@ -171,6 +172,7 @@ let s:filename_checks = { \ 'factor': ['file.factor'], \ 'falcon': ['file.fal'], \ 'fan': ['file.fan', 'file.fwt'], + \ 'fennel': ['file.fnl'], \ 'fetchmail': ['.fetchmailrc'], \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'focexec': ['file.fex', 'file.focexec'], @@ -332,7 +334,7 @@ let s:filename_checks = { \ 'pamconf': ['/etc/pam.conf'], \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'], \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], - \ 'pascal': ['file.pas', 'file.pp', 'file.dpr', 'file.lpr'], + \ 'pascal': ['file.pas', 'file.dpr', 'file.lpr'], \ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'], \ 'pbtxt': ['file.pbtxt'], \ 'pccts': ['file.g'], @@ -368,11 +370,13 @@ let s:filename_checks = { \ 'proto': ['file.proto'], \ 'protocols': ['/etc/protocols'], \ 'psf': ['file.psf'], + \ 'puppet': ['file.pp'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'], \ 'radiance': ['file.rad', 'file.mat'], \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'], + \ 'rbs': ['file.rbs'], \ 'rc': ['file.rc', 'file.rch'], \ 'rcs': ['file,v'], \ 'readline': ['.inputrc', 'inputrc'], @@ -389,7 +393,7 @@ let s:filename_checks = { \ 'rpl': ['file.rpl'], \ 'rst': ['file.rst'], \ 'rtf': ['file.rtf'], - \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake'], + \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile'], \ 'rust': ['file.rs'], \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], @@ -428,6 +432,7 @@ let s:filename_checks = { \ 'smith': ['file.smt', 'file.smith'], \ 'sml': ['file.sml'], \ 'snobol4': ['file.sno', 'file.spt'], + \ 'sparql': ['file.rq', 'file.sparql'], \ 'spec': ['file.spec'], \ 'spice': ['file.sp', 'file.spice'], \ 'spup': ['file.speedup', 'file.spdata', 'file.spd'], @@ -619,6 +624,8 @@ let s:script_checks = { \ 'cpp': [['// Standard iostream objects -*- C++ -*-'], \ ['// -*- C++ -*-']], \ 'yaml': [['%YAML 1.2']], + \ 'pascal': [['#!/path/instantfpc']], + \ 'fennel': [['#!/path/fennel']], \ } " Various forms of "env" optional arguments. @@ -689,5 +696,50 @@ func Test_ts_file() filetype off endfunc +func Test_ttl_file() + filetype on + + call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('turtle', &filetype) + bwipe! + + call writefile(['looks like Tera Term Language'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('teraterm', &filetype) + bwipe! + + call delete('Xfile.ttl') + filetype off +endfunc + +func Test_pp_file() + filetype on + + call writefile(['looks like puppet'], 'Xfile.pp') + split Xfile.pp + call assert_equal('puppet', &filetype) + bwipe! + + let g:filetype_pp = 'pascal' + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + " Test dist#ft#FTpp() + call writefile(['{ pascal comment'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call writefile(['procedure pascal'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call delete('Xfile.pp') + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 88ce64b9eb..2d058e8e32 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -822,4 +822,39 @@ func Test_fold_create_delete() bwipe! endfunc +func Test_fold_relative_move() + enew! + set fdm=indent sw=2 wrap tw=80 + + let content = [ ' foo', ' bar', ' baz', + \ repeat('x', &columns + 1), + \ ' foo', ' bar', ' baz' + \ ] + call append(0, content) + + normal zM + + call cursor(3, 1) + call assert_true(foldclosed(line('.'))) + normal gj + call assert_equal(2, winline()) + + call cursor(2, 1) + call assert_true(foldclosed(line('.'))) + normal 2gj + call assert_equal(3, winline()) + + call cursor(5, 1) + call assert_true(foldclosed(line('.'))) + normal gk + call assert_equal(3, winline()) + + call cursor(6, 1) + call assert_true(foldclosed(line('.'))) + normal 2gk + call assert_equal(2, winline()) + + set fdm& sw& wrap& tw& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 8f6834c2ab..4cc4d775d1 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -2,6 +2,7 @@ source view_util.vim source screendump.vim +source check.vim func Test_highlight() " basic test if ":highlight" doesn't crash @@ -610,3 +611,16 @@ func Test_1_highlight_Normalgroup_exists() call assert_match('hi Normal\s*font=.*', hlNormal) endif endfunc + +" Test for using RGB color values in a highlight group +func Test_xxlast_highlight_RGB_color() + CheckCanRunGui + gui -f + hi MySearch guifg=#110000 guibg=#001100 guisp=#000011 + call assert_equal('#110000', synIDattr(synIDtrans(hlID('MySearch')), 'fg#')) + call assert_equal('#001100', synIDattr(synIDtrans(hlID('MySearch')), 'bg#')) + call assert_equal('#000011', synIDattr(synIDtrans(hlID('MySearch')), 'sp#')) + hi clear +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index e25fe33bb7..2fd82a4b6d 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -171,6 +171,11 @@ func Test_delmarks() " Deleting an already deleted mark should not fail. delmarks x + " getpos() should return all zeros after deleting a filemark. + norm mA + delmarks A + call assert_equal([0, 0, 0, 0], getpos("'A")) + " Test deleting a range of marks. norm ma norm mb diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index f71da92bf8..88320e0c22 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -291,6 +291,32 @@ endfunc endif +func Test_mkview_open_folds() + enew! + + call append(0, ['a', 'b', 'c']) + 1,3fold + " zR affects 'foldlevel', make sure the option is applied after the folds + " have been recreated. + normal zR + write! Xtestfile + + call assert_equal(-1, foldclosed(1)) + call assert_equal(-1, foldclosed(2)) + call assert_equal(-1, foldclosed(3)) + + mkview! Xtestview + source Xtestview + + call assert_equal(-1, foldclosed(1)) + call assert_equal(-1, foldclosed(2)) + call assert_equal(-1, foldclosed(3)) + + call delete('Xtestview') + call delete('Xtestfile') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index ccc5e6ffc9..5a10c9baa6 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -266,8 +266,14 @@ func Test_set_errors() call assert_fails('set foldmarker=x', 'E536:') call assert_fails('set commentstring=x', 'E537:') call assert_fails('set complete=x', 'E539:') + call assert_fails('set rulerformat=%-', 'E539:') + call assert_fails('set rulerformat=%(', 'E542:') + call assert_fails('set rulerformat=%15(%%', 'E542:') + call assert_fails('set statusline=%$', 'E539:') call assert_fails('set statusline=%{', 'E540:') call assert_fails('set statusline=%(', 'E542:') + call assert_fails('set statusline=%)', 'E542:') + if has('cursorshape') " This invalid value for 'guicursor' used to cause Vim to crash. call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') @@ -281,6 +287,21 @@ func Test_set_errors() call assert_fails('set winminwidth=10 winwidth=9', 'E592:') call assert_fails("set showbreak=\x01", 'E595:') call assert_fails('set t_foo=', 'E846:') + if has('python') || has('python3') + call assert_fails('set pyxversion=6', 'E474:') + endif + call assert_fails("let &tabstop='ab'", 'E521:') + call assert_fails('set sessionoptions=curdir,sesdir', 'E474:') + call assert_fails('set foldmarker={{{,', 'E474:') + call assert_fails('set sessionoptions=sesdir,curdir', 'E474:') + call assert_fails('set listchars=trail:· ambiwidth=double', 'E834:') + set listchars& + call assert_fails('set fillchars=stl:· ambiwidth=double', 'E835:') + set fillchars& + call assert_fails('set fileencoding=latin1,utf-8', 'E474:') + set nomodifiable + call assert_fails('set fileencoding=latin1', 'E21:') + set modifiable& endfunc " Must be executed before other tests that set 'term'. @@ -309,12 +330,10 @@ func Test_set_ttytype() set ttytype=xterm call assert_equal('xterm', &ttytype) call assert_equal(&ttytype, &term) - " "set ttytype=" gives E522 instead of E529 - " in travis on some builds. Why? Catch both for now try set ttytype= call assert_report('set ttytype= did not fail') - catch /E529\|E522/ + catch /E529/ endtry " Some systems accept any terminal name and return dumb settings, diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 48c0a83053..704fdacdcd 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2650,6 +2650,13 @@ func Test_vimgrep() call XvimgrepTests('l') endfunc +func Test_vimgrep_wildcards_expanded_once() + new X[id-01] file.txt + call setline(1, 'some text to search for') + vimgrep text % + bwipe! +endfunc + " Test for incsearch highlighting of the :vimgrep pattern " This test used to cause "E315: ml_get: invalid lnum" errors. func Test_vimgrep_incsearch() @@ -3540,7 +3547,7 @@ func Test_lbuffer_crash() sv Xtest augroup QF_Test au! - au * * bw + au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bw augroup END lbuffer augroup QF_Test @@ -3552,7 +3559,7 @@ endfunc func Test_lexpr_crash() augroup QF_Test au! - au * * call setloclist(0, [], 'f') + au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f') augroup END lexpr "" augroup QF_Test @@ -3587,7 +3594,7 @@ func Test_lvimgrep_crash() sv Xtest augroup QF_Test au! - au * * call setloclist(0, [], 'f') + au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f') augroup END lvimgrep quickfix test_quickfix.vim augroup QF_Test @@ -3889,7 +3896,7 @@ func Test_lbuffer_with_bwipe() new new augroup nasty - au * * bwipe + au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bwipe augroup END lbuffer augroup nasty @@ -3902,9 +3909,9 @@ endfunc func Xexpr_acmd_freelist(cchar) call s:setup_commands(a:cchar) - " This was using freed memory. + " This was using freed memory (but with what events?) augroup nasty - au * * call g:Xsetlist([], 'f') + au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call g:Xsetlist([], 'f') augroup END Xexpr "x" augroup nasty diff --git a/src/nvim/testdir/test_sleep.vim b/src/nvim/testdir/test_sleep.vim new file mode 100644 index 0000000000..f71855fd4b --- /dev/null +++ b/src/nvim/testdir/test_sleep.vim @@ -0,0 +1,26 @@ +" Test for sleep and sleep! commands + +func! s:get_time_ms() + let timestr = reltimestr(reltime()) + let dotidx = stridx(timestr, '.') + let sec = str2nr(timestr[:dotidx]) + let msec = str2nr(timestr[dotidx + 1:]) + return (sec * 1000) + (msec / 1000) +endfunc + +func! s:assert_takes_longer(cmd, time_ms) + let start = s:get_time_ms() + execute a:cmd + let end = s:get_time_ms() + call assert_true(end - start >=# a:time_ms) +endfun + +func! Test_sleep_bang() + call s:assert_takes_longer('sleep 50m', 50) + call s:assert_takes_longer('sleep! 50m', 50) + call s:assert_takes_longer('sl 50m', 50) + call s:assert_takes_longer('sl! 50m', 50) + call s:assert_takes_longer('1sleep', 1000) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index e7f332bc4c..e6ad92f483 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -735,4 +735,82 @@ func Test_x_arg() call delete('Xtest_x_arg') endfunc +" Test starting vim with various names: vim, ex, view, evim, etc. +func Test_progname() + CheckUnix + + call mkdir('Xprogname', 'p') + call writefile(['silent !date', + \ 'call writefile([mode(1), ' + \ .. '&insertmode, &diff, &readonly, &updatecount, ' + \ .. 'join(split(execute("message"), "\n")[1:])], "Xprogname_out")', + \ 'qall'], 'Xprogname_after') + + " +---------------------------------------------- progname + " | +--------------------------------- mode(1) + " | | +--------------------------- &insertmode + " | | | +---------------------- &diff + " | | | | +----------------- &readonly + " | | | | | +-------- &updatecount + " | | | | | | +--- :messages + " | | | | | | | + " let expectations = { + " \ 'vim': ['n', '0', '0', '0', '200', ''], + " \ 'gvim': ['n', '0', '0', '0', '200', ''], + " \ 'ex': ['ce', '0', '0', '0', '200', ''], + " \ 'exim': ['cv', '0', '0', '0', '200', ''], + " \ 'view': ['n', '0', '0', '1', '10000', ''], + " \ 'gview': ['n', '0', '0', '1', '10000', ''], + " \ 'evim': ['n', '1', '0', '0', '200', ''], + " \ 'eview': ['n', '1', '0', '1', '10000', ''], + " \ 'rvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'], + " \ 'rgvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'], + " \ 'rview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'], + " \ 'rgview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'], + " \ 'vimdiff': ['n', '0', '1', '0', '200', ''], + " \ 'gvimdiff': ['n', '0', '1', '0', '200', '']} + let expectations = {'nvim': ['n', '0', '0', '0', '200', '']} + + " let prognames = ['vim', 'gvim', 'ex', 'exim', 'view', 'gview', + " \ 'evim', 'eview', 'rvim', 'rgvim', 'rview', 'rgview', + " \ 'vimdiff', 'gvimdiff'] + let prognames = ['nvim'] + + for progname in prognames + if empty($DISPLAY) + if progname =~# 'g' + " Can't run gvim, gview (etc.) if $DISPLAY is not setup. + continue + endif + if has('gui') && (progname ==# 'evim' || progname ==# 'eview') + " evim or eview will start the GUI if there is gui support. + " So don't try to start them either if $DISPLAY is not setup. + continue + endif + endif + + exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname + + let stdout_stderr = '' + if progname =~# 'g' + let stdout_stderr = system('Xprogname/'..progname..' -f --clean --not-a-term -S Xprogname_after') + else + exe 'sil !Xprogname/'..progname..' -f --clean -S Xprogname_after' + endif + + if progname =~# 'g' && !has('gui') + call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname) + else + call assert_equal('', stdout_stderr, progname) + call assert_equal(expectations[progname], readfile('Xprogname_out'), progname) + endif + + call delete('Xprogname/' .. progname) + call delete('Xprogname_out') + endfor + + call delete('Xprogname_after') + call delete('Xprogname', 'd') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 687b1cb989..969b75d424 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -513,8 +513,8 @@ func Test_window_colon_command() endfunc func Test_access_freed_mem() - " This was accessing freed memory - au * 0 vs xxx + " This was accessing freed memory (but with what events?) + au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx arg 0 argadd all diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 124f96e039..3e683a4926 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -297,7 +297,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release"); break; case TERMKEY_MOUSE_UNKNOWN: - assert(false); + abort(); } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 9d3ec21949..06efc9fa99 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -157,7 +157,7 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, #ifndef NDEBUG for (size_t i = 0; i < kv_size(layers); i++) { if (kv_A(layers, i) == grid) { - assert(false); + abort(); } } #endif diff --git a/src/nvim/version.c b/src/nvim/version.c index 834d27cc84..deba3f6e49 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -300,7 +300,7 @@ static const int included_patches[] = { 1620, 1619, 1618, - // 1617, + 1617, // 1616, 1615, 1614, @@ -392,7 +392,7 @@ static const int included_patches[] = { 1528, 1527, 1526, - // 1525, + 1525, 1524, 1523, 1522, @@ -462,7 +462,7 @@ static const int included_patches[] = { 1458, 1457, 1456, - // 1455, + 1455, 1454, 1453, 1452, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 01f20cf29a..e70749795b 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -159,6 +159,7 @@ enum { EXPAND_MAPCLEAR, EXPAND_ARGLIST, EXPAND_CHECKHEALTH, + EXPAND_LUA, }; diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 44b6ab5f5a..e9d82ca87d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2078,7 +2078,7 @@ viml_pexpr_parse_process_token: case kExprLexMissing: case kExprLexSpacing: case kExprLexEOC: { - assert(false); + abort(); } case kExprLexInvalid: { ERROR_FROM_TOKEN(cur_token); @@ -3028,7 +3028,7 @@ viml_pexpr_parse_end: // Until trailing "}" it is impossible to distinguish curly braces // identifier and dictionary, so it must not appear in the stack like // this. - assert(false); + abort(); } case kExprNodeInteger: case kExprNodeFloat: @@ -3042,7 +3042,7 @@ viml_pexpr_parse_end: // These are plain values and not containers, for them it should only // be possible to show up in the topmost stack element, but it was // unconditionally popped at the start. - assert(false); + abort(); } case kExprNodeComma: case kExprNodeColon: diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index ab913ba4a4..d2b555ee5b 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1389,6 +1389,40 @@ describe('API/extmarks', function() undo]],false) eq(2, meths.eval('1+1')) -- did not crash end) + + it('works with left and right gravity', function() + -- right gravity should move with inserted text, while + -- left gravity should stay in place. + curbufmeths.set_extmark(ns, 0, 5, {right_gravity = false}) + curbufmeths.set_extmark(ns, 0, 5, {right_gravity = true}) + feed([[Aasdfasdf]]) + + eq({ {1, 0, 5}, {2, 0, 13} }, + curbufmeths.get_extmarks(ns, 0, -1, {})) + + -- but both move when text is inserted before + feed([[<esc>Iasdf<esc>]]) + -- eq({}, curbufmeths.get_lines(0, -1, true)) + eq({ {1, 0, 9}, {2, 0, 17} }, + curbufmeths.get_extmarks(ns, 0, -1, {})) + + -- clear text + curbufmeths.set_text(0, 0, 0, 17, {}) + + -- handles set_text correctly as well + eq({ {1, 0, 0}, {2, 0, 0} }, + meths.buf_get_extmarks(0, ns, 0, -1, {})) + curbufmeths.set_text(0, 0, 0, 0, {'asdfasdf'}) + eq({ {1, 0, 0}, {2, 0, 8} }, + curbufmeths.get_extmarks(ns, 0, -1, {})) + + feed('u') + -- handles pasting + meths.exec([[let @a='asdfasdf']], false) + feed([["ap]]) + eq({ {1, 0, 0}, {2, 0, 8} }, + meths.buf_get_extmarks(0, ns, 0, -1, {})) + end) end) describe('Extmarks buffer api with many marks', function() diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 30128e9c40..3ff3efb8c9 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2002,4 +2002,77 @@ describe('API', function() }, meths.get_option_info'showcmd') end) end) + + describe('nvim_echo', function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Brown, bold = true}, -- Statement + [4] = {foreground = Screen.colors.SlateBlue}, -- Special + }) + command('highlight Statement gui=bold guifg=Brown') + command('highlight Special guifg=SlateBlue') + end) + + it('should clear cmdline message before echo', function() + feed(':call nvim_echo([["msg"]], v:false, {})<CR>') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + msg | + ]]} + end) + + it('can show highlighted line', function() + nvim_async("echo", {{"msg_a"}, {"msg_b", "Statement"}, {"msg_c", "Special"}}, true, {}) + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + msg_a{3:msg_b}{4:msg_c} | + ]]} + end) + + it('can show highlighted multiline', function() + nvim_async("echo", {{"msg_a\nmsg_a", "Statement"}, {"msg_b", "Special"}}, true, {}) + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {2: }| + {3:msg_a} | + {3:msg_a}{4:msg_b} | + {1:Press ENTER or type command to continue}^ | + ]]} + end) + + it('can save message history', function() + nvim('command', 'set cmdheight=2') -- suppress Press ENTER + nvim("echo", {{"msg\nmsg"}, {"msg"}}, true, {}) + eq("msg\nmsgmsg", meths.exec('messages', true)) + end) + + it('can disable saving message history', function() + nvim('command', 'set cmdheight=2') -- suppress Press ENTER + nvim_async("echo", {{"msg\nmsg"}, {"msg"}}, false, {}) + eq("", meths.exec("messages", true)) + end) + end) end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 6d1182478a..b59d87eb12 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -31,9 +31,9 @@ describe('jobs', function() nvim('set_var', 'channel', channel) source([[ function! Normalize(data) abort - " Windows: remove ^M + " Windows: remove ^M and term escape sequences return type([]) == type(a:data) - \ ? map(a:data, 'substitute(v:val, "\r", "", "g")') + \ ? map(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")') \ : a:data endfunction function! OnEvent(id, data, event) dict @@ -63,6 +63,7 @@ describe('jobs', function() it('append environment #env', function() nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") if iswin() then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) @@ -75,8 +76,24 @@ describe('jobs', function() }) end) + it('append environment with pty #env', function() + nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") + nvim('command', "let g:job_opts.pty = v:true") + nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") + if iswin() then + nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) + else + nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) + end + expect_msg_seq({ + {'notification', 'stdout', {0, {'hello world abc', ''}}}, + }) + end) + it('replace environment #env', function() nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") nvim('command', "let g:job_opts.clear_env = 1") diff --git a/test/functional/eval/environ_spec.lua b/test/functional/eval/environ_spec.lua index 54d2dc960b..9e19568249 100644 --- a/test/functional/eval/environ_spec.lua +++ b/test/functional/eval/environ_spec.lua @@ -3,6 +3,11 @@ local clear = helpers.clear local eq = helpers.eq local environ = helpers.funcs.environ local exists = helpers.funcs.exists +local system = helpers.funcs.system +local nvim_prog = helpers.nvim_prog +local command = helpers.command +local eval = helpers.eval +local setenv = helpers.funcs.setenv describe('environment variables', function() it('environ() handles empty env variable', function() @@ -17,3 +22,59 @@ describe('environment variables', function() eq(0, exists('$DOES_NOT_EXIST')) end) end) + +describe('empty $HOME', function() + local original_home = os.getenv('HOME') + + -- recover $HOME after each test + after_each(function() + if original_home ~= nil then + setenv('HOME', original_home) + end + os.remove('test_empty_home') + os.remove('./~') + end) + + local function tilde_in_cwd() + -- get files in cwd + command("let test_empty_home_cwd_files = split(globpath('.', '*'), '\n')") + -- get the index of the file named '~' + command('let test_empty_home_tilde_index = index(test_empty_home_cwd_files, "./~")') + return eval('test_empty_home_tilde_index') ~= -1 + end + + local function write_and_test_tilde() + system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', + '-c', 'write test_empty_home', '+q'}) + eq(false, tilde_in_cwd()) + end + + it("'~' folder not created in cwd if $HOME and related env not defined", function() + command("unlet $HOME") + write_and_test_tilde() + + command("let $HOMEDRIVE='C:'") + command("let $USERPROFILE='C:\\'") + write_and_test_tilde() + + command("unlet $HOMEDRIVE") + write_and_test_tilde() + + command("unlet $USERPROFILE") + write_and_test_tilde() + + command("let $HOME='%USERPROFILE%'") + command("let $USERPROFILE='C:\\'") + write_and_test_tilde() + end) + + it("'~' folder not created in cwd if writing a file with invalid $HOME", function() + setenv('HOME', '/path/does/not/exist') + write_and_test_tilde() + end) + + it("'~' folder not created in cwd if writing a file with $HOME=''", function() + command("let $HOME=''") + write_and_test_tilde() + end) +end) diff --git a/test/functional/legacy/backspace_opt_spec.lua b/test/functional/legacy/backspace_opt_spec.lua deleted file mode 100644 index 90bc6f74f0..0000000000 --- a/test/functional/legacy/backspace_opt_spec.lua +++ /dev/null @@ -1,67 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local call, clear = helpers.call, helpers.clear -local source, eq, nvim = helpers.source, helpers.eq, helpers.meths - -describe("test 'backspace' settings", function() - before_each(function() - clear() - - source([[ - func Exec(expr) - let str='' - try - exec a:expr - catch /.*/ - let str=v:exception - endtry - return str - endfunc - - func Test_backspace_option() - set backspace= - call assert_equal('', &backspace) - set backspace=indent - call assert_equal('indent', &backspace) - set backspace=eol - call assert_equal('eol', &backspace) - set backspace=start - call assert_equal('start', &backspace) - " Add the value - set backspace= - set backspace=indent - call assert_equal('indent', &backspace) - set backspace+=eol - call assert_equal('indent,eol', &backspace) - set backspace+=start - call assert_equal('indent,eol,start', &backspace) - " Delete the value - set backspace-=indent - call assert_equal('eol,start', &backspace) - set backspace-=start - call assert_equal('eol', &backspace) - set backspace-=eol - call assert_equal('', &backspace) - " Check the error - call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474')) - call assert_equal(0, match(Exec('set backspace+=def'), '.*E474')) - " NOTE: Vim doesn't check following error... - "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) - - " Check backwards compatibility with version 5.4 and earlier - set backspace=0 - call assert_equal('0', &backspace) - set backspace=1 - call assert_equal('1', &backspace) - set backspace=2 - call assert_equal('2', &backspace) - call assert_false(match(Exec('set backspace=3'), '.*E474')) - call assert_false(match(Exec('set backspace=10'), '.*E474')) - endfunc - ]]) - end) - - it('works', function() - call('Test_backspace_option') - eq({}, nvim.get_vvar('errors')) - end) -end) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 67dc5f5a16..6087f9e7d6 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -20,12 +20,13 @@ local origlines = {"original line 1", "original line 6", " indented line"} -local function attach_buffer(evname) - exec_lua([[ +before_each(function () + clear() + exec_lua [[ local evname = ... local events = {} - function test_register(bufnr, id, changedtick, utf_sizes, preview) + function test_register(bufnr, evname, id, changedtick, utf_sizes, preview) local function callback(...) table.insert(events, {id, ...}) if test_unreg == id then @@ -44,41 +45,30 @@ local function attach_buffer(evname) events = {} return ret_events end - ]], evname) -end + ]] +end) describe('lua buffer event callbacks: on_lines', function() - before_each(function() - clear() - attach_buffer('on_lines') - end) - - - -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot - -- assert the wrong thing), but masks errors with unflushed lines (as - -- nvim_buf_get_offset forces a flush of the memline). To be safe run the - -- test both ways. - local function check(verify,utf_sizes) + local function setup_eventcheck(verify, utf_sizes, lines) local lastsize - meths.buf_set_lines(0, 0, -1, true, origlines) + meths.buf_set_lines(0, 0, -1, true, lines) if verify then lastsize = meths.buf_get_offset(0, meths.buf_line_count(0)) end - exec_lua("return test_register(...)", 0, "test1",false,utf_sizes) - local tick = meths.buf_get_changedtick(0) - + exec_lua("return test_register(...)", 0, "on_lines", "test1",false,utf_sizes) local verify_name = "test1" + local function check_events(expected) local events = exec_lua("return get_events(...)" ) if utf_sizes then -- this test case uses ASCII only, so sizes should be the same. -- Unicode is tested below. for _, event in ipairs(expected) do - event[9] = event[8] - event[10] = event[8] + event[9] = event[9] or event[8] + event[10] = event[10] or event[9] end end - eq(expected, events) + expect_events(expected, events, "line updates") if verify then for _, event in ipairs(events) do if event[1] == verify_name and event[2] == "lines" then @@ -92,25 +82,38 @@ describe('lua buffer event callbacks: on_lines', function() end end end + return check_events, function(new) verify_name = new end + end + + -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot + -- assert the wrong thing), but masks errors with unflushed lines (as + -- nvim_buf_get_offset forces a flush of the memline). To be safe run the + -- test both ways. + local function check(verify,utf_sizes) + local check_events, verify_name = setup_eventcheck(verify, utf_sizes, origlines) + + local tick = meths.buf_get_changedtick(0) command('set autoindent') command('normal! GyyggP') tick = tick + 1 - check_events({{ "test1", "lines", 1, tick, 0, 0, 1, 0}}) + check_events {{ "test1", "lines", 1, tick, 0, 0, 1, 0}} meths.buf_set_lines(0, 3, 5, true, {"changed line"}) tick = tick + 1 - check_events({{ "test1", "lines", 1, tick, 3, 5, 4, 32 }}) + check_events {{ "test1", "lines", 1, tick, 3, 5, 4, 32 }} - exec_lua("return test_register(...)", 0, "test2", true, utf_sizes) + exec_lua("return test_register(...)", 0, "on_lines", "test2", true, utf_sizes) tick = tick + 1 command('undo') -- plugins can opt in to receive changedtick events, or choose -- to only receive actual changes. - check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 }, - { "test2", "lines", 1, tick, 3, 4, 5, 13 }, - { "test2", "changedtick", 1, tick+1 } }) + check_events { + { "test1", "lines", 1, tick, 3, 4, 5, 13 }; + { "test2", "lines", 1, tick, 3, 4, 5, 13 }; + { "test2", "changedtick", 1, tick+1 }; + } tick = tick + 1 -- simulate next callback returning true @@ -121,38 +124,40 @@ describe('lua buffer event callbacks: on_lines', function() -- plugins can opt in to receive changedtick events, or choose -- to only receive actual changes. - check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 }, - { "test2", "lines", 1, tick, 6, 7, 9, 16 }}) + check_events { + { "test1", "lines", 1, tick, 6, 7, 9, 16 }; + { "test2", "lines", 1, tick, 6, 7, 9, 16 }; + } - verify_name = "test2" + verify_name "test2" meths.buf_set_lines(0, 1, 1, true, {"added"}) tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 1, 1, 2, 0 }}) + check_events {{ "test2", "lines", 1, tick, 1, 1, 2, 0 }} feed('wix') tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 16 }}) + check_events {{ "test2", "lines", 1, tick, 4, 5, 5, 16 }} -- check hot path for multiple insert feed('yz') tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 17 }}) + check_events {{ "test2", "lines", 1, tick, 4, 5, 5, 17 }} feed('<bs>') tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 19 }}) + check_events {{ "test2", "lines", 1, tick, 4, 5, 5, 19 }} feed('<esc>Go') tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 11, 11, 12, 0 }}) + check_events {{ "test2", "lines", 1, tick, 11, 11, 12, 0 }} feed('x') tick = tick + 1 - check_events({{ "test2", "lines", 1, tick, 11, 12, 12, 5 }}) + check_events {{ "test2", "lines", 1, tick, 11, 12, 12, 5 }} command('bwipe!') - check_events({{ "test2", "detach", 1 }}) + check_events {{ "test2", "detach", 1 }} end it('works', function() @@ -167,51 +172,63 @@ describe('lua buffer event callbacks: on_lines', function() check(false,true) end) - it('works with utf_sizes and unicode text', function() + local function check_unicode(verify) local unicode_text = {"ascii text", "latin text åäö", "BMP text ɧ αλφά", "BMP text 汉语 ↥↧", "SMP 🤦 🦄🦃", "combining å بِيَّة"} - meths.buf_set_lines(0, 0, -1, true, unicode_text) - feed('gg') - exec_lua("return test_register(...)", 0, "test1", false, true) + local check_events, verify_name = setup_eventcheck(verify, true, unicode_text) + local tick = meths.buf_get_changedtick(0) - feed('dd') + feed('ggdd') tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 0, 1, 0, 11, 11, 11 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 0, 1, 0, 11, 11, 11 }} feed('A<bs>') tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 0, 1, 1, 18, 15, 15 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 0, 1, 1, 18, 15, 15 }} feed('<esc>jylp') tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 1, 2, 2, 21, 16, 16 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 1, 2, 2, 21, 16, 16 }} feed('+eea<cr>') tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 2, 3, 4, 23, 15, 15 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 2, 3, 4, 23, 15, 15 }} feed('<esc>jdw') tick = tick + 1 -- non-BMP chars count as 2 UTF-2 codeunits - eq({{ "test1", "lines", 1, tick, 4, 5, 5, 18, 9, 12 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 4, 5, 5, 18, 9, 12 }} feed('+rx') tick = tick + 1 -- count the individual codepoints of a composed character. - eq({{ "test1", "lines", 1, tick, 5, 6, 6, 27, 20, 20 }}, exec_lua("return get_events(...)" )) + check_events {{ "test1", "lines", 1, tick, 5, 6, 6, 27, 20, 20 }} feed('kJ') tick = tick + 1 + -- verification fails with multiple line updates, sorry about that + verify_name "" -- NB: this is inefficient (but not really wrong). - eq({{ "test1", "lines", 1, tick, 4, 5, 5, 14, 5, 8 }, - { "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" )) + check_events { + { "test1", "lines", 1, tick, 4, 5, 5, 14, 5, 8 }; + { "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }; + } + end + + it('works with utf_sizes and unicode text', function() + check_unicode(false) + end) + + it('works with utf_sizes and unicode text with verify', function() + check_unicode(true) end) + it('has valid cursor position while shifting', function() meths.buf_set_lines(0, 0, -1, true, {'line1'}) exec_lua([[ @@ -272,11 +289,6 @@ describe('lua buffer event callbacks: on_lines', function() end) describe('lua: nvim_buf_attach on_bytes', function() - before_each(function() - clear() - attach_buffer('on_bytes') - end) - -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot -- assert the wrong thing), but masks errors with unflushed lines (as -- nvim_buf_get_offset forces a flush of the memline). To be safe run the @@ -291,7 +303,7 @@ describe('lua: nvim_buf_attach on_bytes', function() local len = meths.buf_get_offset(0, meths.buf_line_count(0)) eq(len == -1 and 1 or len, string.len(shadowbytes)) end - exec_lua("return test_register(...)", 0, "test1", false, false, true) + exec_lua("return test_register(...)", 0, "on_bytes", "test1", false, false, true) meths.buf_get_changedtick(0) local verify_name = "test1" @@ -504,11 +516,13 @@ describe('lua: nvim_buf_attach on_bytes', function() feed ':%s/bcd/' check_events { { "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 0, 0, 0, 3, 3 }; } feed 'a' check_events { { "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 3, 3 }; } end) diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua new file mode 100644 index 0000000000..3ba7e1589f --- /dev/null +++ b/test/functional/lua/command_line_completion_spec.lua @@ -0,0 +1,171 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec_lua = helpers.exec_lua + +local get_completions = function(input, env) + return exec_lua("return {vim._expand_pat(...)}", '^' .. input, env) +end + +local get_compl_parts = function(parts) + return exec_lua("return {vim._expand_pat_get_parts(...)}", parts) +end + +before_each(clear) + +describe('nlua_expand_pat', function() + it('should complete exact matches', function() + eq({{'exact'}, 0}, get_completions('exact', { exact = true })) + end) + + it('should return empty table when nothing matches', function() + eq({{}, 0}, get_completions('foo', { bar = true })) + end) + + it('should return nice completions with function call prefix', function() + eq({{'FOO'}, 6}, get_completions('print(F', { FOO = true, bawr = true })) + end) + + it('should return keys for nested dictionaries', function() + eq( + {{ + 'nvim_buf_set_lines', + 'nvim_buf_set_option' + }, 8 + }, + get_completions('vim.api.nvim_buf_', { + vim = { + api = { + nvim_buf_set_lines = true, + nvim_buf_set_option = true, + nvim_win_doesnt_match = true, + }, + other_key = true, + } + }) + ) + end) + + it('it should work with colons', function() + eq( + {{ + 'bawr', + 'baz', + }, 8 + }, + get_completions('MyClass:b', { + MyClass = { + baz = true, + bawr = true, + foo = false, + } + }) + ) + end) + + it('should return keys for string reffed dictionaries', function() + eq( + {{ + 'nvim_buf_set_lines', + 'nvim_buf_set_option' + }, 11 + }, + get_completions('vim["api"].nvim_buf_', { + vim = { + api = { + nvim_buf_set_lines = true, + nvim_buf_set_option = true, + nvim_win_doesnt_match = true, + }, + other_key = true, + } + }) + ) + end) + + it('should return keys for string reffed dictionaries', function() + eq( + {{ + 'nvim_buf_set_lines', + 'nvim_buf_set_option' + }, 21 + }, + get_completions('vim["nested"]["api"].nvim_buf_', { + vim = { + nested = { + api = { + nvim_buf_set_lines = true, + nvim_buf_set_option = true, + nvim_win_doesnt_match = true, + }, + }, + other_key = true, + } + }) + ) + end) + + it('should be able to interpolate globals', function() + eq( + {{ + 'nvim_buf_set_lines', + 'nvim_buf_set_option' + }, 12 + }, + get_completions('vim[MY_VAR].nvim_buf_', { + MY_VAR = "api", + vim = { + api = { + nvim_buf_set_lines = true, + nvim_buf_set_option = true, + nvim_win_doesnt_match = true, + }, + other_key = true, + } + }) + ) + end) + + it('should return everything if the input is of length 0', function() + eq({{"other", "vim"}, 0}, get_completions('', { vim = true, other = true })) + end) + + describe('get_parts', function() + it('should return an empty list for no separators', function() + eq({{}, 1}, get_compl_parts("vim")) + end) + + it('just the first item before a period', function() + eq({{"vim"}, 5}, get_compl_parts("vim.ap")) + end) + + it('should return multiple parts just for period', function() + eq({{"vim", "api"}, 9}, get_compl_parts("vim.api.nvim_buf")) + end) + + it('should be OK with colons', function() + eq({{"vim", "api"}, 9}, get_compl_parts("vim:api.nvim_buf")) + end) + + it('should work for just one string ref', function() + eq({{"vim", "api"}, 12}, get_compl_parts("vim['api'].nvim_buf")) + end) + + it('should work for just one string ref, with double quote', function() + eq({{"vim", "api"}, 12}, get_compl_parts('vim["api"].nvim_buf')) + end) + + it('should allows back-to-back string ref', function() + eq({{"vim", "nested", "api"}, 22}, get_compl_parts('vim["nested"]["api"].nvim_buf')) + end) + + it('should allows back-to-back string ref with spaces before and after', function() + eq({{"vim", "nested", "api"}, 25}, get_compl_parts('vim[ "nested" ]["api"].nvim_buf')) + end) + + it('should allow VAR style loolup', function() + eq({{"vim", {"NESTED"}, "api"}, 20}, get_compl_parts('vim[NESTED]["api"].nvim_buf')) + end) + end) +end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 92d077ed14..eb5e284385 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -204,9 +204,8 @@ describe('startup defaults', function() end) describe('$NVIM_LOG_FILE', function() - local datasubdir = iswin() and 'nvim-data' or 'nvim' local xdgdir = 'Xtest-startup-xdg-logpath' - local xdgdatadir = xdgdir..'/'..datasubdir + local xdgcachedir = xdgdir..'/nvim' after_each(function() os.remove('Xtest-logpath') rmdir(xdgdir) @@ -218,28 +217,21 @@ describe('startup defaults', function() }}) eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) end) - it('defaults to stdpath("data")/log if empty', function() - eq(true, mkdir(xdgdir) and mkdir(xdgdatadir)) + it('defaults to stdpath("cache")/log if empty', function() + eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) clear({env={ - XDG_DATA_HOME=xdgdir, + XDG_CACHE_HOME=xdgdir, NVIM_LOG_FILE='', -- Empty is invalid. }}) - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) - it('defaults to stdpath("data")/log if invalid', function() - eq(true, mkdir(xdgdir) and mkdir(xdgdatadir)) + it('defaults to stdpath("cache")/log if invalid', function() + eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) clear({env={ - XDG_DATA_HOME=xdgdir, + XDG_CACHE_HOME=xdgdir, NVIM_LOG_FILE='.', -- Any directory is invalid. }}) - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) - end) - it('defaults to .nvimlog if stdpath("data") is invalid', function() - clear({env={ - XDG_DATA_HOME='Xtest-missing-xdg-dir', - NVIM_LOG_FILE='.', -- Any directory is invalid. - }}) - eq('.nvimlog', eval('$NVIM_LOG_FILE')) + eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) end) end) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 3a676359ab..4705a76465 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -12,41 +12,41 @@ describe('vim.lsp.diagnostic', function() clear() exec_lua [[ - require('vim.lsp') - - make_range = function(x1, y1, x2, y2) - return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } - end - - make_error = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 1, - } - end - - make_warning = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 2, - } - end - - make_information = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 3, - } - end - - count_of_extmarks_for_client = function(bufnr, client_id) - return #vim.api.nvim_buf_get_extmarks( - bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {} - ) - end + require('vim.lsp') + + make_range = function(x1, y1, x2, y2) + return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } + end + + make_error = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 1, + } + end + + make_warning = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 2, + } + end + + make_information = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 3, + } + end + + count_of_extmarks_for_client = function(bufnr, client_id) + return #vim.api.nvim_buf_get_extmarks( + bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {} + ) + end ]] fake_uri = "file://fake/uri" @@ -640,6 +640,36 @@ describe('vim.lsp.diagnostic', function() eq(expected_spacing, #spacing) end) + + it('allows filtering via severity limit', function() + local get_extmark_count_with_severity = function(severity_limit) + return exec_lua([[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + underline = false, + virtual_text = { + severity_limit = ... + }, + }) + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_warning('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + + return count_of_extmarks_for_client(diagnostic_bufnr, 1) + ]], severity_limit) + end + + -- No messages with Error or higher + eq(0, get_extmark_count_with_severity("Error")) + + -- But now we don't filter it + eq(1, get_extmark_count_with_severity("Warning")) + eq(1, get_extmark_count_with_severity("Hint")) + end) end) describe('lsp.util.show_line_diagnostics', function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ec06cb0639..981e2a96a8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -193,6 +193,12 @@ describe('LSP', function() end) describe('basic_init test', function() + after_each(function() + stop() + exec_lua("lsp.stop_client(lsp.get_active_clients())") + exec_lua("lsp._vim_exit_handler()") + end) + it('should run correctly', function() local expected_callbacks = { {NIL, "test", {}, 1}; @@ -303,6 +309,19 @@ describe('LSP', function() end; } end) + it('workspace/configuration returns NIL per section if client was started without config.settings', function() + clear() + fake_lsp_server_setup('workspace/configuration no settings') + eq({ NIL, NIL, }, exec_lua [[ + local params = { + items = { + {section = 'foo'}, + {section = 'bar'}, + } + } + return vim.lsp.handlers['workspace/configuration'](nil, nil, params, TEST_RPC_CLIENT_ID) + ]]) + end) it('should verify capabilities sent', function() local expected_callbacks = { @@ -1045,7 +1064,7 @@ describe('LSP', function() return { edits = { make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄") - }, + }, textDocument = { uri = "file://fake/uri"; version = editVersion @@ -1086,7 +1105,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(...) + vim.lsp.util.apply_text_document_edit(args[1]) ]], edit, versionedBuf) end @@ -1109,6 +1128,7 @@ describe('LSP', function() }, buf_lines(target_bufnr)) end) end) + describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() local expected = { @@ -1124,6 +1144,106 @@ describe('LSP', function() ]]) end) end) + + describe('apply_workspace_edit', function() + local replace_line_edit = function(row, new_line, editVersion) + return { + edits = { + -- NOTE: This is a hack if you have a line longer than 1000 it won't replace it + make_edit(row, 0, row, 1000, new_line) + }, + textDocument = { + uri = "file://fake/uri"; + version = editVersion + } + } + end + + -- Some servers send all the edits separately, but with the same version. + -- We should not stop applying the edits + local make_workspace_edit = function(changes) + return { + documentChanges = changes + } + end + + local target_bufnr, changedtick = nil, nil + + before_each(function() + local ret = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = { + "Original Line #1", + "Original Line #2" + } + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + local update_changed_tick = function() + vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + end + + update_changed_tick() + vim.api.nvim_buf_attach(bufnr, false, { + on_changedtick = function() + update_changed_tick() + end + }) + + return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} + ]] + + target_bufnr = ret[1] + changedtick = ret[2] + end) + + it('apply_workspace_edit applies a single edit', function() + local new_lines = { + "First Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq({ + "First Line", + "Original Line #2", + }, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + + it('apply_workspace_edit applies multiple edits', function() + local new_lines = { + "First Line", + "Second Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq(new_lines, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + end) + describe('completion_list_to_complete_items', function() -- Completion option precedence: -- textEdit.newText > insertText > label diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 520574c08a..f99362fbdf 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -245,7 +245,7 @@ void ui_refresh(void) parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do + for pattern, match in cquery:iter_matches(tree:root(), 0) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid,node in pairs(match) do @@ -289,7 +289,7 @@ void ui_refresh(void) local query = query.parse_query("c", ...) local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do table.insert(nodes, {node:range()}) end @@ -365,7 +365,7 @@ void ui_refresh(void) query = vim.treesitter.parse_query("c", "(declaration) @decl") local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do table.insert(nodes, node) end @@ -412,7 +412,7 @@ void ui_refresh(void) local nodes = {} local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') - for _, node in query:iter_captures(parser:parse()[1]:root(), str, 0, 2) do + for _, node in query:iter_captures(parser:parse()[1]:root(), str) do table.insert(nodes, { node:range() }) end @@ -421,6 +421,29 @@ void ui_refresh(void) eq({ {0, 10, 0, 13} }, ret) end) + it("should use node range when omitted", function() + local txt = [[ + int foo = 42; + int bar = 13; + ]] + + local ret = exec_lua([[ + local str = ... + local parser = vim.treesitter.get_string_parser(str, "c") + + local nodes = {} + local query = vim.treesitter.parse_query("c", '((identifier) @foo)') + local first_child = parser:parse()[1]:root():child(1) + + for _, node in query:iter_captures(first_child, str) do + table.insert(nodes, { node:range() }) + end + + return nodes]], txt) + + eq({ {1, 10, 1, 13} }, ret) + end) + describe("when creating a language tree", function() local function get_ranges() return exec_lua([[ @@ -539,7 +562,7 @@ int x = INT_MAX; query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! "key" "value"))') parser = vim.treesitter.get_parser(0, "c") - for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0, 0, 1) do + for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do result = metadata.key end @@ -562,7 +585,7 @@ int x = INT_MAX; query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value"))') parser = vim.treesitter.get_parser(0, "c") - for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0, 0, 1) do + for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do result = metadata[pattern].key end diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index d1b8de5e4e..1937102782 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -266,6 +266,111 @@ describe('Signs', function() ]]} end) + it('auto-resize sign column with minimum size (#13783)', function() + feed('ia<cr>b<cr>c<cr><esc>') + command('set number') + -- sign column should always accommodate at the minimum size + command('set signcolumn=auto:1-3') + screen:expect([[ + {2: }{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: }{6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- should support up to 8 signs at minimum + command('set signcolumn=auto:8-9') + screen:expect([[ + {2: }{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: }{6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- should keep the same sign size when signs are not exceeding + -- the minimum + command('set signcolumn=auto:2-5') + command('sign define pietSearch text=>> texthl=Search') + command('sign place 1 line=1 name=pietSearch buffer=1') + screen:expect([[ + {1:>>}{2: }{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: }{6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- should resize itself when signs are exceeding minimum but + -- not over the maximum + command('sign place 2 line=1 name=pietSearch buffer=1') + command('sign place 3 line=1 name=pietSearch buffer=1') + command('sign place 4 line=1 name=pietSearch buffer=1') + screen:expect([[ + {1:>>>>>>>>}{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: }{6:^ 4 } | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- should keep the column at maximum size when signs are + -- exceeding the maximum + command('sign place 5 line=1 name=pietSearch buffer=1') + command('sign place 6 line=1 name=pietSearch buffer=1') + command('sign place 7 line=1 name=pietSearch buffer=1') + command('sign place 8 line=1 name=pietSearch buffer=1') + screen:expect([[ + {1:>>>>>>>>>>}{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: ^ }{6: 4 } | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('ignores signs with no icon and text when calculting the signcolumn width', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index 01fc50289d..a4241fe5aa 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed = helpers.clear, helpers.feed local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local feed_command, source, expect = helpers.feed_command, helpers.source, helpers.expect +local funcs = helpers.funcs local curbufmeths = helpers.curbufmeths local command = helpers.command local meths = helpers.meths @@ -26,6 +27,7 @@ describe('completion', function() [7] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [8] = {reverse = true}, [9] = {bold = true, reverse = true}, + [10] = {foreground = Screen.colors.Grey0, background = Screen.colors.Yellow}, }) end) @@ -895,8 +897,47 @@ describe('completion', function() ]]) end) - describe('from the commandline window', function() + describe('lua completion', function() + it('expands when there is only one match', function() + feed(':lua CURRENT_TESTING_VAR = 1<CR>') + feed(':lua CURRENT_TESTING_<TAB>') + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :lua CURRENT_TESTING_VAR^ | + ]]} + end) + it('expands when there is only one match', function() + feed(':lua CURRENT_TESTING_FOO = 1<CR>') + feed(':lua CURRENT_TESTING_BAR = 1<CR>') + feed(':lua CURRENT_TESTING_<TAB>') + screen:expect{ grid = [[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {10:CURRENT_TESTING_BAR}{9: CURRENT_TESTING_FOO }| + :lua CURRENT_TESTING_BAR^ | + ]], unchanged = true } + end) + + it('provides completion from `getcompletion()`', function() + eq({'vim'}, funcs.getcompletion('vi', 'lua')) + eq({'api'}, funcs.getcompletion('vim.ap', 'lua')) + eq({'tbl_filter'}, funcs.getcompletion('vim.tbl_fil', 'lua')) + eq({'vim'}, funcs.getcompletion('print(vi', 'lua')) + end) + end) + + describe('from the commandline window', function() it('is cleared after CTRL-C', function () feed('q:') feed('ifoo faa fee f') diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index f9bc3fabc4..fd1c5ee4b9 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -62,7 +62,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, goto vim_str2nr_dec; } default: { - assert(false); + abort(); } } } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) @@ -102,7 +102,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - assert(false); // Should’ve used goto earlier. + abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c index df422cea3e..1614f813d7 100644 --- a/test/symbolic/klee/nvim/memory.c +++ b/test/symbolic/klee/nvim/memory.c @@ -45,7 +45,7 @@ void xfree(void *const p) return; } } - assert(false); + abort(); } void *xrealloc(void *const p, size_t new_size) @@ -63,7 +63,7 @@ void *xrealloc(void *const p, size_t new_size) return ret; } } - assert(false); + abort(); return (void *)(intptr_t)1; } diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 5af5eb0a49..aab8eb4464 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -196,11 +196,11 @@ set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47 set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) -set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/6002fcd.tar.gz) -set(TREESITTER_C_SHA256 46f8d44fa886d9ddb92571bb6fa8b175992c8758eca749cb1217464e512b6e97) +set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/99151b1.tar.gz) +set(TREESITTER_C_SHA256 950386f9ba77fb6a7e992198d4f219c34238a2bbc005c5f53c4212d0f8772b06) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/0.17.3.zip) -set(TREESITTER_SHA256 19068b6663f5a4cacd5d805fa437419e3c29eb615ed9523e438b400b79f39c20) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/0.18.0.zip) +set(TREESITTER_SHA256 ac53b7708ca47161dac7f8e852bd61accb8527d45b7ad72e29e12e8e72dbe440) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |