diff options
320 files changed, 17400 insertions, 4641 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/ISSUE_TEMPLATE/lsp_bug_report.md b/.github/ISSUE_TEMPLATE/lsp_bug_report.md index 660fbc3eed..d2488a14e8 100644 --- a/.github/ISSUE_TEMPLATE/lsp_bug_report.md +++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.md @@ -6,7 +6,12 @@ labels: bug, lsp --- -<!-- Before reporting: search existing issues and check the FAQ. --> +<!-- +Before reporting: search existing issues and check the FAQ. Usage questions +such as "How do I...?" or "Why isn't X language server/feature working?" belong +on the [Neovim Discourse](https://neovim.discourse.group/c/7-category/7) and will +be closed. +--> - `nvim --version`: - language server name/version: @@ -34,10 +39,17 @@ You can find the location of the log with the following command. </details> -### Steps to reproduce using `nvim -u NORC` +### Steps to reproduce using nvim -u minimal_init.lua +<!-- + Note, if the issue is with an autocompletion or other LSP plugin, please + report to the upstream tracker. Download the minmal config with + wget https://raw.githubusercontent.com/neovim/nvim-lspconfig/master/test/minimal_init.lua + and modify it to include any specific commands or servers pertaining to your issues. +--> + ``` -nvim -u NORC +nvim -u minimal_init.lua ``` ### Actual behaviour diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44a911b21b..bd90aeb932 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: runner: macos-10.15 os: osx runs-on: ${{ matrix.runner }} + if: github.event.pull_request.draft == false env: CC: ${{ matrix.cc }} CI_OS_NAME: ${{ matrix.os }} @@ -88,6 +89,7 @@ jobs: windows: runs-on: windows-2016 + if: github.event.pull_request.draft == false env: DEPS_BUILD_DIR: "C:/projects/nvim-deps" DEPS_PREFIX: "C:/projects/nvim-deps/usr" diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml new file mode 100644 index 0000000000..431ccd8b61 --- /dev/null +++ b/.github/workflows/nightly.yaml @@ -0,0 +1,49 @@ +name: Nightly +on: + schedule: + - cron: '3 3 * * *' + +jobs: + update-vim-patches: + runs-on: ubuntu-20.04 + env: + VIM_SOURCE_DIR: ${{ format('{0}/vim-src', github.workspace) }} + VERSION_BRANCH: marvim/ci-version-update + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/checkout@v2 + with: + repository: vim/vim + path: ${{ env.VIM_SOURCE_DIR }} + fetch-depth: 0 + + - run: | + gh release download -R neovim/neovim -p nvim.appimage + chmod a+x nvim.appimage + mkdir -p $HOME/.local/bin + mv nvim.appimage $HOME/.local/bin/nvim + printf '%s\n' "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Setup git config + run: | + git config --global user.name 'marvim' + git config --global user.email 'marvim@users.noreply.github.com' + + - name: Update src/version.c + id: update-version + run: | + git checkout -b ${VERSION_BRANCH} + nvim -i NONE -u NONE --headless +'luafile scripts/vimpatch.lua' +q + printf '::set-output name=NEW_PATCHES::%s\n' $([ -z "$(git diff)" ]; echo $?) + + - name: Automatic PR + if: ${{ steps.update-version.outputs.NEW_PATCHES != 0 }} + run: | + git add -u + git commit -m 'version.c: update [skip ci]' + git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VERSION_BRANCH} + gh pr create --fill --label vim-patch --base master --head ${VERSION_BRANCH} || true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 753142e555..43fe1d5101 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-multilib gettext gperf libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip - name: Build release id: build run: | @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake gcc-multilib gettext gperf libtool-bin locales ninja-build pkg-config unzip + sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: make appimage-latest - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') @@ -156,7 +156,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - delete_release: '' + delete_release: true tag_name: nightly - uses: meeDamian/github-release@2.0 with: diff --git a/.gitignore b/.gitignore index ab301bd336..1eb382424b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ compile_commands.json /.deps/ /tmp/ /.clangd/ +/.cache/clangd/ .DS_Store *.mo @@ -28,13 +29,13 @@ compile_commands.json /.nvimlog # Generated by scripts/vim-patch.sh -/.vim-src/ +/.vim-src # Generated by old (Vim) tests. /src/nvim/testdir/del /src/nvim/testdir/test*.out /src/nvim/testdir/test*.res -/src/nvim/testdir/test.log +/src/nvim/testdir/test*.log /src/nvim/testdir/messages /src/nvim/testdir/viminfo /src/nvim/testdir/test.ok diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c67c55cd..c22ab8dbae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") endif() if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # Ignore case when comparing filenames on Windows and Mac. + set(CASE_INSENSITIVE_FILENAME TRUE) # Enable fixing case-insensitive filenames for Windows and Mac. set(USE_FNAME_CASE TRUE) endif() @@ -677,3 +679,14 @@ set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_NSIS_MODIFY_PATH ON) set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) include(CPack) + +#add uninstall target +if(NOT TARGET uninstall) + configure_file( + "cmake/UninstallHelper.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/UninstallHelper.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/UninstallHelper.cmake) +endif() 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 @@ -52,7 +52,10 @@ Pre-built packages for Windows, macOS, and Linux are found on the Install from source ------------------- +See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page for details. + The build is CMake-based, but a Makefile is provided as a convenience. +After installing the dependencies, run the following command. make CMAKE_BUILD_TYPE=RelWithDebInfo sudo make install @@ -62,29 +65,12 @@ To install to a non-default location: make CMAKE_INSTALL_PREFIX=/full/path/ make install -To skip bundled (`third-party/*`) dependencies: - -1. Install the dependencies using a package manager. - ``` - sudo apt install gperf luajit luarocks libuv1-dev libluajit-5.1-dev libunibilium-dev libmsgpack-dev libtermkey-dev libvterm-dev libutf8proc-dev - sudo luarocks build mpack - sudo luarocks build lpeg - sudo luarocks build inspect - ``` -2. Build with `USE_BUNDLED=OFF`: - ``` - make CMAKE_BUILD_TYPE=RelWithDebInfo USE_BUNDLED=OFF - sudo make install - ``` - To inspect the build, these CMake features are useful: - `cmake --build build --target help` lists all build targets. - `build/CMakeCache.txt` (or `cmake -LAH build/`) contains the resolved values of all CMake variables. - `build/compile_commands.json` shows the full compiler invocations for each translation unit. -See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page for details. - Transitioning from Vim -------------------- diff --git a/ci/build.ps1 b/ci/build.ps1 index db7026ac66..53e4328e02 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -88,11 +88,14 @@ elseif ($compiler -eq 'MSVC') { if (-not $NoTests) { # Setup python (use AppVeyor system python) - C:\hostedtoolcache\windows\Python\2.7.18\x64\python.exe -m pip install pynvim ; exitIfFailed - C:\hostedtoolcache\windows\Python\3.5.4\x64\python.exe -m pip install pynvim ; exitIfFailed - # Disambiguate python3 - move C:\hostedtoolcache\windows\Python\3.5.4\x64\python.exe C:\hostedtoolcache\windows\Python\3.5.4\x64\python3.exe - $env:PATH = "C:\hostedtoolcache\windows\Python\3.5.4\x64;C:\hostedtoolcache\windows\Python\2.7.18\x64;$env:PATH" + # Disambiguate python3, if needed + if (-not (Test-Path -Path C:\hostedtoolcache\windows\Python\3.5.4\x64\python3.exe) ) { + move C:\hostedtoolcache\windows\Python\3.5.4\x64\python.exe C:\hostedtoolcache\windows\Python\3.5.4\x64\python3.exe + } + $env:PATH = "C:\hostedtoolcache\windows\Python\2.7.18\x64;C:\hostedtoolcache\windows\Python\3.5.4\x64;$env:PATH" + + python -m pip install pynvim ; exitIfFailed + python3 -m pip install pynvim ; exitIfFailed # Sanity check python -c "import pynvim; print(str(pynvim))" ; exitIfFailed python3 -c "import pynvim; print(str(pynvim))" ; exitIfFailed diff --git a/ci/common/test.sh b/ci/common/test.sh index 118e181dfa..92c15c8ba1 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -34,7 +34,7 @@ check_core_dumps() { cores="$(find /cores/ -type f -print)" local _sudo='sudo' else - cores="$(find ./ -type f -name 'core.*' -print)" + cores="$(find ./ -type f \( -name 'core.*' -o -name core -o -name nvim.core \) -print)" local _sudo= fi diff --git a/cmake/UninstallHelper.cmake.in b/cmake/UninstallHelper.cmake.in new file mode 100644 index 0000000000..c2d34d4796 --- /dev/null +++ b/cmake/UninstallHelper.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 8a70d864c4..6b88c92cf0 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -52,6 +52,7 @@ check_function_exists(setsid HAVE_SETSID) check_function_exists(sigaction HAVE_SIGACTION) check_function_exists(strcasecmp HAVE_STRCASECMP) check_function_exists(strncasecmp HAVE_STRNCASECMP) +check_function_exists(strptime HAVE_STRPTIME) # Symbols check_symbol_exists(FD_CLOEXEC "fcntl.h" HAVE_FD_CLOEXEC) diff --git a/config/config.h.in b/config/config.h.in index 95e2c872a3..502f84bbcf 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -33,6 +33,7 @@ #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRINGS_H #cmakedefine HAVE_STRNCASECMP +#cmakedefine HAVE_STRPTIME #cmakedefine HAVE_SYS_SDT_H #cmakedefine HAVE_SYS_UTSNAME_H #cmakedefine HAVE_SYS_WAIT_H @@ -40,6 +41,7 @@ #cmakedefine HAVE_WORKING_LIBINTL #cmakedefine HAVE_WSL #cmakedefine UNIX +#cmakedefine CASE_INSENSITIVE_FILENAME #cmakedefine USE_FNAME_CASE #cmakedefine HAVE_SYS_UIO_H #ifdef HAVE_SYS_UIO_H diff --git a/contrib/flake.lock b/contrib/flake.lock new file mode 100644 index 0000000000..521b7629d9 --- /dev/null +++ b/contrib/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1610051610, + "narHash": "sha256-U9rPz/usA1/Aohhk7Cmc2gBrEEKRzcW4nwPWMPwja4Y=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3982c9903e93927c2164caa727cd3f6a0e6d14cc", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1613226215, + "narHash": "sha256-3rA5cGIrBHD6yeKhNhsF7/t461ww25oJY8KyBb0IhjU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ff96a0fa5635770390b184ae74debea75c3fd534", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/contrib/flake.nix b/contrib/flake.nix index d18534215c..e75ff0356b 100644 --- a/contrib/flake.nix +++ b/contrib/flake.nix @@ -27,22 +27,31 @@ }); # a development binary to help debug issues - neovim-debug = (neovim.override { - stdenv = if pkgs.stdenv.isLinux then pkgs.llvmPackages_latest.stdenv else pkgs.stdenv; + neovim-debug = let + stdenv = pkgs.stdenvAdapters.keepDebugInfo (if pkgs.stdenv.isLinux then pkgs.llvmPackages_latest.stdenv else pkgs.stdenv); + in + pkgs.enableDebugging ((neovim.override { lua = pkgs.enableDebugging pkgs.luajit; + inherit stdenv; }).overrideAttrs (oa: { cmakeBuildType = "Debug"; cmakeFlags = oa.cmakeFlags ++ [ "-DMIN_LOG_LEVEL=0" ]; - }); - # for neovim developers, very slow + disallowedReferences = []; + })); + + # for neovim developers, builds a slow binary + # huge closure size but aims at covering all scripts # brings development tools as well neovim-developer = let lib = nixpkgs.lib; - pythonEnv = pkgs.python3; + pythonEnv = pkgs.python3.withPackages(ps: [ + ps.msgpack + ps.flake8 # for 'make pylint' + ]); luacheck = pkgs.luaPackages.luacheck; in (neovim-debug.override ({ doCheck = pkgs.stdenv.isLinux; })).overrideAttrs (oa: { @@ -51,7 +60,7 @@ "-DMIN_LOG_LEVEL=0" "-DENABLE_LTO=OFF" "-DUSE_BUNDLED=OFF" - ] ++ pkgs.stdenv.lib.optionals pkgs.stdenv.isLinux [ + ] ++ pkgs.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" @@ -61,7 +70,9 @@ pythonEnv include-what-you-use # for scripts/check-includes.py jq # jq for scripts/vim-patch.sh -r - doxygen + shellcheck # for `make shlint` + doxygen # for script/gen_vimdoc.py + clang-tools # for clangd to find the correct headers ]); shellHook = oa.shellHook + '' @@ -97,6 +108,5 @@ defaultApp = apps.nvim; devShell = pkgs.neovim-developer; - } - ); + }); } diff --git a/runtime/autoload/haskellcomplete.vim b/runtime/autoload/haskellcomplete.vim index 520ab93700..48fbac7f9f 100644 --- a/runtime/autoload/haskellcomplete.vim +++ b/runtime/autoload/haskellcomplete.vim @@ -2,7 +2,7 @@ " Language: Haskell " Maintainer: Daniel Campoverde <alx@sillybytes.net> " URL: https://github.com/alx741/haskellcomplete.vim -" Last Change: 2018 Aug 26 +" Last Change: 2019 May 14 " Usage: setlocal omnifunc=haskellcomplete#Complete @@ -63,6 +63,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, extension) endif endfor + let b:completingLangExtension = 0 return l:matches endif @@ -78,6 +79,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, flag) endif endfor + let b:completingOptionsGHC = 0 return l:matches endif @@ -93,6 +95,7 @@ function! haskellcomplete#Complete(findstart, base) call add(l:matches, module) endif endfor + let b:completingModule = 0 return l:matches endif diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 8b734bbb6f..0bb343e198 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -42,7 +42,7 @@ function! s:check_config() abort endif let writeable = v:true - let shadafile = substitute(matchstr( + let shadafile = empty(&shada) ? &shada : substitute(matchstr( \ split(&shada, ',')[-1], '^n.\+'), '^n', '', '') let shadafile = empty(&shadafile) ? empty(shadafile) ? \ stdpath('data').'/shada/main.shada' : expand(shadafile) @@ -247,6 +247,10 @@ function! s:check_terminal() abort let kdch1_entry = matchstr(out, 'key_dc=[^,[:space:]]*') if v:shell_error + \ && (!has('win32') + \ || empty(matchstr(out, + \ 'infocmp: couldn''t open terminfo file .\+' + \ ..'\%(conemu\|vtpcon\|win32con\)'))) call health#report_error('command failed: '.cmd."\n".out) else call health#report_info('key_backspace (kbs) terminfo entry: ' diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 112dd4354f..de540405e6 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -400,8 +400,6 @@ function! s:check_python(version) abort endfor endif - let pip = 'pip' . (a:version == 2 ? '' : '3') - if empty(python_exe) " No Python executable can import 'neovim'. Check if any Python executable " can import 'pynvim'. If so, that Python failed to import 'neovim' as @@ -413,9 +411,9 @@ function! s:check_python(version) abort \ 'Detected pip upgrade failure: Python executable can import "pynvim" but ' \ . 'not "neovim": '. pynvim_exe, \ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n" - \ . pip ." uninstall pynvim neovim\n" - \ . pip ." install pynvim\n" - \ . pip ." install neovim # only if needed by third-party software") + \ . pynvim_exe ." -m pip uninstall pynvim neovim\n" + \ . pynvim_exe ." -m pip install pynvim\n" + \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software") endif else let [pyversion, current, latest, status] = s:version_info(python_exe) @@ -440,7 +438,7 @@ function! s:check_python(version) abort if s:is_bad_response(current) call health#report_error( \ "pynvim is not installed.\nError: ".current, - \ ['Run in shell: '. pip .' install pynvim']) + \ ['Run in shell: '. python_exe .' -m pip install pynvim']) endif if s:is_bad_response(latest) diff --git a/runtime/autoload/health/treesitter.vim b/runtime/autoload/health/treesitter.vim new file mode 100644 index 0000000000..5f167310ce --- /dev/null +++ b/runtime/autoload/health/treesitter.vim @@ -0,0 +1,5 @@ +function! health#treesitter#check() abort + call health#report_start('Checking treesitter configuration') + lua require 'vim.treesitter.health'.check_health() +endfunction + diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 78a86315a3..c629923cd3 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -51,7 +51,7 @@ function! man#open_page(count, mods, ...) abort let [l:buf, l:save_tfu] = [bufnr(), &tagfunc] try - set tagfunc=man#goto_tag + setlocal tagfunc=man#goto_tag let l:target = l:name . '(' . l:sect . ')' if a:mods !~# 'tab' && s:find_man() execute 'silent keepalt tag' l:target diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index b69ad7187a..7a799abb13 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -1,8 +1,8 @@ " netrw.vim: Handles file transfer and remote directory listing across " AUTOLOAD SECTION -" Date: Jul 16, 2019 -" Version: 165 -" Maintainer: Charles E Campbell <NdrOchip@ScampbellPfamily.AbizM-NOSPAM> +" Date: Jan 07, 2020 +" Version: 168 +" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim " Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, @@ -13,6 +13,10 @@ " expressed or implied. By using this plugin, you agree that " in no event will the copyright holder be liable for any damages " resulting from the use of this software. +" +" Note: the code here was started in 1999 under a much earlier version of vim. The directory browsing +" code was written using vim v6, which did not have Lists (Lists were first offered with vim-v7). +" "redraw!|call DechoSep()|call inputsave()|call input("Press <cr> to continue")|call inputrestore() " " But be doers of the Word, and not only hearers, deluding your own selves {{{1 @@ -39,7 +43,7 @@ if exists("s:needspatches") endfor endif -let g:loaded_netrw = "v165" +let g:loaded_netrw = "v168" if !exists("s:NOTE") let s:NOTE = 0 let s:WARNING = 1 @@ -64,7 +68,7 @@ setl cpo&vim " Usage: netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,"some message",error-number) " netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,["message1","message2",...],error-number) " (this function can optionally take a list of messages) -" Mar 21, 2017 : max errnum currently is 105 +" Dec 2, 2019 : max errnum currently is 106 fun! netrw#ErrorMsg(level,msg,errnum) " call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow) @@ -232,12 +236,12 @@ if !exists("g:netrw_ftp_options") let g:netrw_ftp_options= "-i -n" endif if !exists("g:netrw_http_cmd") - if executable("curl") - let g:netrw_http_cmd = "curl" - call s:NetrwInit("g:netrw_http_xcmd","-L -o") - elseif executable("wget") + if executable("wget") let g:netrw_http_cmd = "wget" call s:NetrwInit("g:netrw_http_xcmd","-q -O") + elseif executable("curl") + let g:netrw_http_cmd = "curl" + call s:NetrwInit("g:netrw_http_xcmd","-L -o") elseif executable("elinks") let g:netrw_http_cmd = "elinks" call s:NetrwInit("g:netrw_http_xcmd","-source >") @@ -443,23 +447,9 @@ if !exists("g:netrw_localmovecmd") let g:netrw_localmovecmd= "" endif endif -if v:version < 704 || (v:version == 704 && !has("patch1107")) - " 1109 provides for delete(tmpdir,"d") which is what will be used - if exists("g:netrw_local_rmdir") - let g:netrw_localrmdir= g:netrw_local_rmdir - call netrw#ErrorMsg(s:NOTE,"g:netrw_local_rmdir is deprecated in favor of g:netrw_localrmdir",86) - endif - if has("win32") || has("win95") || has("win64") || has("win16") - if g:netrw_cygwin - call s:NetrwInit("g:netrw_localrmdir","rmdir") - else - let g:netrw_localrmdir = expand("$COMSPEC") - let g:netrw_localrmdiropt= " /c rmdir" - endif - else - call s:NetrwInit("g:netrw_localrmdir","rmdir") - endif -endif +" following serves as an example for how to insert a version&patch specific test +"if v:version < 704 || (v:version == 704 && !has("patch1107")) +"endif call s:NetrwInit("g:netrw_liststyle" , s:THINLIST) " sanity checks if g:netrw_liststyle < 0 || g:netrw_liststyle >= s:MAXLIST @@ -1500,7 +1490,8 @@ fun! netrw#Obtain(islocal,fname,...) " -i : turns off interactive prompting from ftp " -n unix : DON'T use <.netrc>, even though it exists " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1Gdd + " Note: using "_dd to delete to the black hole register; avoids messing up @@ + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) if getline(1) !~ "^$" @@ -1614,7 +1605,6 @@ fun! s:NetrwOptionsSave(vt) let {a:vt}netrw_cpokeep = &l:cpo let {a:vt}netrw_diffkeep = &l:diff let {a:vt}netrw_fenkeep = &l:fen -" call Decho("saving current settings: got here#1",'~'.expand("<slnum>")) if !exists("g:netrw_ffkeep") || g:netrw_ffkeep let {a:vt}netrw_ffkeep = &l:ff endif @@ -1633,7 +1623,6 @@ fun! s:NetrwOptionsSave(vt) let {a:vt}netrw_rokeep = &l:ro let {a:vt}netrw_selkeep = &l:sel let {a:vt}netrw_spellkeep = &l:spell -" call Decho("saving current settings: got here#2",'~'.expand("<slnum>")) if !g:netrw_use_noswf let {a:vt}netrw_swfkeep = &l:swf endif @@ -1647,6 +1636,7 @@ fun! s:NetrwOptionsSave(vt) " call Decho("saving a few selected netrw-related variables",'~'.expand("<slnum>")) if g:netrw_keepdir let {a:vt}netrw_dirkeep = getcwd() +" call Decho("saving to ".a:vt."netrw_dirkeep<".{a:vt}netrw_dirkeep.">",'~'.expand("<slnum>")) endif sil! let {a:vt}netrw_slashkeep= @/ @@ -1713,6 +1703,7 @@ fun! s:NetrwOptionsRestore(vt) " call Dfunc("s:NetrwOptionsRestore(vt<".a:vt.">) win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> winnr($)=".winnr("$")) " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>")) if !exists("{a:vt}netrw_optionsave") +" call Decho("case ".a:vt."netrw_optionsave : doesn't exist",'~'.expand("<slnum>")) " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>")) " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("<slnum>")) " call Dret("s:NetrwOptionsRestore : ".a:vt."netrw_optionsave doesn't exist") @@ -1854,7 +1845,7 @@ endfun " Used by s:NetrwOptionsRestore() to restore each netrw-senstive setting " keepvars are set up by s:NetrwOptionsSave fun! s:NetrwRestoreSetting(keepvar,setting) -"" call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)") +""" call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)") " typically called from s:NetrwOptionsRestore " call s:NetrwRestoreSettings(keep-option-variable-name,'associated-option') @@ -1869,7 +1860,7 @@ fun! s:NetrwRestoreSetting(keepvar,setting) "" call Decho("fyi: a:setting<".a:setting."> setting<".setting.">") if setting != keepvarval -"" call Decho("restore setting<".a:setting."=".setting."> to keepvarval<".keepvarval.">") +"" call Decho("restore setting<".a:setting."> (currently=".setting.") to keepvarval<".keepvarval.">") if type(a:setting) == 0 exe "let ".a:setting."= ".keepvarval elseif type(a:setting) == 1 @@ -2159,7 +2150,7 @@ fun! netrw#NetRead(mode,...) " -i : turns off interactive prompting from ftp " -n unix : DON'T use <.netrc>, even though it exists " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1Gdd + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) if getline(1) !~ "^$" @@ -2259,7 +2250,7 @@ fun! netrw#NetRead(mode,...) NetrwKeepj put ='quit' " perform cadaver operation: - NetrwKeepj norm! 1Gdd + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) keepj bd! endif @@ -2575,7 +2566,7 @@ fun! netrw#NetWrite(...) range " -i : turns off interactive prompting from ftp " -n unix : DON'T use <.netrc>, even though it exists " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1Gdd + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) if getline(1) !~ "^$" @@ -2641,7 +2632,7 @@ fun! netrw#NetWrite(...) range NetrwKeepj put ='put '.tmpfile.' '.netrw_fname " perform cadaver operation: - NetrwKeepj norm! 1Gdd + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd) " remove enew buffer (quietly) @@ -2827,7 +2818,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method) " readcmd=='t': simply do nothing if a:readcmd == 't' " call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("<slnum>")) -" call Dret("NetrwGetFile : skip read of <".a:tfile.">") +" call Dret("NetrwGetFile : skip read of tfile<".a:tfile.">") return endif @@ -4323,7 +4314,7 @@ fun! s:NetrwGetWord() let curline= getline('.') if curline =~# '"\s*Sorted by\s' - NetrwKeepj norm! s + NetrwKeepj norm! "_s let s:netrw_skipbrowse= 1 echo 'Pressing "s" also works' @@ -5158,17 +5149,31 @@ fun! s:NetrwBrowseUpDir(islocal) endfun " --------------------------------------------------------------------- -" netrw#BrowseX: (implements "x") executes a special "viewer" script or program for the {{{2 +" netrw#BrowseX: (implements "x" and "gx") executes a special "viewer" script or program for the {{{2 " given filename; typically this means given their extension. " 0=local, 1=remote fun! netrw#BrowseX(fname,remote) -" call Dfunc("netrw#BrowseX(fname<".a:fname."> remote=".a:remote.")") - - " if its really just a local directory, then do a "gf" instead - if (a:remote == 0 && isdirectory(a:fname)) || (a:remote == 1 && a:fname =~ '/$' && a:fname !~ '^https\=:') + let use_ctrlo= 1 +" call Dfunc("netrw#BrowseX(fname<".a:fname."> remote=".a:remote.") implements x and gx maps") + + if a:remote == 0 && isdirectory(a:fname) + " if its really just a local directory, then do a "gf" instead +" call Decho("remote≡0 and a:fname<".a:fname."> ".(isdirectory(a:fname)? "is a directory" : "is not a directory"),'~'.expand("<slnum>")) +" call Decho("..appears to be a local directory; using e ".a:fname." instead",'~'.expand("<slnum>")) + exe "e ".a:fname +" call Dret("netrw#BrowseX") + return + elseif a:remote == 1 && a:fname !~ '^https\=:' && a:fname =~ '/$' + " remote directory, not a webpage access, looks like an attempt to do a directory listing +" call Decho("remote≡1 and a:fname<".a:fname.">",'~'.expand("<slnum>")) +" call Decho("..and fname ".((a:fname =~ '^https\=:')? 'matches' : 'does not match').'^https\=:','~'.expand("<slnum>")) +" call Decho("..and fname ".((a:fname =~ '/$')? 'matches' : 'does not match').' /$','~'.expand("<slnum>")) +" call Decho("..appears to be a remote directory listing request; using gf instead",'~'.expand("<slnum>")) norm! gf -" call Dret("(netrw#BrowseX) did gf instead") +" call Dret("netrw#BrowseX") + return endif +" call Decho("not a local file nor a webpage request",'~'.expand("<slnum>")) let ykeep = @@ let screenposn = winsaveview() @@ -5266,10 +5271,9 @@ fun! netrw#BrowseX(fname,remote) endif " call Decho("set up redirection: redir{".redir."} srr{".&srr."}",'~'.expand("<slnum>")) - " extract any viewing options. Assumes that they're set apart by quotes. -" call Decho("extract any viewing options",'~'.expand("<slnum>")) + " extract any viewing options. Assumes that they're set apart by spaces. if exists("g:netrw_browsex_viewer") -" call Decho("g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) +" call Decho("extract any viewing options from g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) if g:netrw_browsex_viewer =~ '\s' let viewer = substitute(g:netrw_browsex_viewer,'\s.*$','','') let viewopt = substitute(g:netrw_browsex_viewer,'^\S\+\s*','','')." " @@ -5292,16 +5296,16 @@ fun! netrw#BrowseX(fname,remote) " execute the file handler " call Decho("execute the file handler (if any)",'~'.expand("<slnum>")) if exists("g:netrw_browsex_viewer") && g:netrw_browsex_viewer == '-' -" call Decho("g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) let ret= netrwFileHandlers#Invoke(exten,fname) elseif exists("g:netrw_browsex_viewer") && executable(viewer) -" call Decho("g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">",'~'.expand("<slnum>")) call s:NetrwExe("sil !".viewer." ".viewopt.s:ShellEscape(fname,1).redir) let ret= v:shell_error elseif has("win32") || has("win64") -" call Decho("win".(has("win32")? "32" : "64")",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) win".(has("win32")? "32" : "64"),'~'.expand("<slnum>")) if executable("start") call s:NetrwExe('sil! !start rundll32 url.dll,FileProtocolHandler '.s:ShellEscape(fname,1)) elseif executable("rundll32") @@ -5309,56 +5313,68 @@ fun! netrw#BrowseX(fname,remote) else call netrw#ErrorMsg(s:WARNING,"rundll32 not on path",74) endif - " call inputsave()|call input("Press <cr> to continue")|call inputrestore() let ret= v:shell_error elseif has("win32unix") let winfname= 'c:\cygwin'.substitute(fname,'/','\\','g') -" call Decho("cygwin: winfname<".s:ShellEscape(winfname,1).">",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) cygwin: winfname<".s:ShellEscape(winfname,1).">",'~'.expand("<slnum>")) if executable("start") +" call Decho("(netrw#BrowseX) win32unix+start",'~'.expand("<slnum>")) call s:NetrwExe('sil !start rundll32 url.dll,FileProtocolHandler '.s:ShellEscape(winfname,1)) elseif executable("rundll32") +" call Decho("(netrw#BrowseX) win32unix+rundll32",'~'.expand("<slnum>")) call s:NetrwExe('sil !rundll32 url.dll,FileProtocolHandler '.s:ShellEscape(winfname,1)) elseif executable("cygstart") +" call Decho("(netrw#BrowseX) win32unix+cygstart",'~'.expand("<slnum>")) call s:NetrwExe('sil !cygstart '.s:ShellEscape(fname,1)) else call netrw#ErrorMsg(s:WARNING,"rundll32 not on path",74) endif - " call inputsave()|call input("Press <cr> to continue")|call inputrestore() let ret= v:shell_error elseif has("unix") && executable("kfmclient") && s:CheckIfKde() -" call Decho("unix and kfmclient",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) unix and kfmclient",'~'.expand("<slnum>")) call s:NetrwExe("sil !kfmclient exec ".s:ShellEscape(fname,1)." ".redir) let ret= v:shell_error elseif has("unix") && executable("exo-open") && executable("xdg-open") && executable("setsid") -" call Decho("unix, exo-open, xdg-open",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) unix, exo-open, xdg-open",'~'.expand("<slnum>")) call s:NetrwExe("sil !setsid xdg-open ".s:ShellEscape(fname,1).redir) let ret= v:shell_error elseif has("unix") && $DESKTOP_SESSION == "mate" && executable("atril") -" call Decho("unix and atril",'~'.expand("<slnum>")) - call s:NetrwExe("sil !atril ".s:ShellEscape(fname,1).redir) +" call Decho("(netrw#BrowseX) unix and atril",'~'.expand("<slnum>")) + if a:fname =~ '^https\=://' + " atril does not appear to understand how to handle html -- so use gvim to edit the document + let use_ctrlo= 0 +" call Decho("(COMBAK) fname<".fname.">") +" call Decho("(COMBAK) a:fname<".a:fname.">") + call s:NetrwExe("sil! !gvim ".fname.' -c "keepj keepalt file '.fnameescape(a:fname).'"') + + else + call s:NetrwExe("sil !atril ".s:ShellEscape(fname,1).redir) + endif let ret= v:shell_error elseif has("unix") && executable("xdg-open") -" call Decho("unix and xdg-open",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) unix and xdg-open",'~'.expand("<slnum>")) call s:NetrwExe("sil !xdg-open ".s:ShellEscape(fname,1).redir) let ret= v:shell_error elseif has("macunix") && executable("open") -" call Decho("macunix and open",'~'.expand("<slnum>")) +" call Decho("(netrw#BrowseX) macunix and open",'~'.expand("<slnum>")) call s:NetrwExe("sil !open ".s:ShellEscape(fname,1)." ".redir) let ret= v:shell_error else " netrwFileHandlers#Invoke() always returns 0 +" call Decho("(netrw#BrowseX) use netrwFileHandlers",'~'.expand("<slnum>")) let ret= netrwFileHandlers#Invoke(exten,fname) endif " if unsuccessful, attempt netrwFileHandlers#Invoke() if ret +" call Decho("(netrw#BrowseX) ret=".ret," indicates unsuccessful thus far",'~'.expand("<slnum>")) let ret= netrwFileHandlers#Invoke(exten,fname) endif @@ -5380,8 +5396,9 @@ fun! netrw#BrowseX(fname,remote) if g:netrw_use_noswf setl noswf endif - exe "sil! NetrwKeepj norm! \<c-o>" -" redraw! + if use_ctrlo + exe "sil! NetrwKeepj norm! \<c-o>" + endif endif " call Decho("restoring posn to screenposn<".string(screenposn).">",'~'.expand("<slnum>")) call winrestview(screenposn) @@ -5410,11 +5427,11 @@ endfun " netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2 fun! netrw#BrowseXVis() " call Dfunc("netrw#BrowseXVis()") - let atkeep = @@ - norm! gvy -" call Decho("@@<".@@.">",'~'.expand("<slnum>")) - call netrw#BrowseX(@@,netrw#CheckIfRemote(@@)) - let @@ = atkeep + let akeep = @a + norm! gv"ay + let gxfile= @a + let @a = akeep + call netrw#BrowseX(gxfile,netrw#CheckIfRemote(gxfile)) " call Dret("netrw#BrowseXVis") endfun @@ -5764,22 +5781,7 @@ fun! s:NetrwHome() if exists("g:netrw_home") let home= expand(g:netrw_home) else - " go to vim plugin home - for home in split(&rtp,',') + [''] - if isdirectory(s:NetrwFile(home)) && filewritable(s:NetrwFile(home)) | break | endif - let basehome= substitute(home,'[/\\]\.vim$','','') - if isdirectory(s:NetrwFile(basehome)) && filewritable(s:NetrwFile(basehome)) - let home= basehome."/.vim" - break - endif - endfor - if home == "" - " just pick the first directory - let home= substitute(&rtp,',.*$','','') - endif - if (has("win32") || has("win95") || has("win64") || has("win16")) - let home= substitute(home,'/','\\','g') - endif + let home = stdpath('data') endif " insure that the home directory exists if g:netrw_dirhistmax > 0 && !isdirectory(s:NetrwFile(home)) @@ -6039,13 +6041,12 @@ fun! s:NetrwSLeftrelease(islocal) endfun " --------------------------------------------------------------------- -" s:NetrwListHide: uses [range]g~...~d to delete files that match comma {{{2 -" separated patterns given in g:netrw_list_hide +" s:NetrwListHide: uses [range]g~...~d to delete files that match {{{2 +" comma-separated patterns given in g:netrw_list_hide fun! s:NetrwListHide() " call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">") " call Decho("initial: ".string(getline(w:netrw_bannercnt,'$'))) let ykeep= @@ -" call DechoBuf(bufnr("%"),"COMBAK#3") " find a character not in the "hide" string to use as a separator for :g and :v commands " How-it-works: take the hiding command, convert it into a range. @@ -6054,8 +6055,8 @@ fun! s:NetrwListHide() " Use the first character left as a separator character. " call Decho("find a character not in the hide string to use as a separator") let listhide= g:netrw_list_hide - let sep = strpart(substitute('/~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1) -" call Decho("sep=".sep,'~'.expand("<slnum>")) + let sep = strpart(substitute('~@#$%^&*{};:,<.>?|1234567890','['.escape(listhide,'-]^\').']','','ge'),1,1) +" call Decho("sep=".sep," (sep not in hide string)'~'.expand("<slnum>")) while listhide != "" if listhide =~ ',' @@ -6065,10 +6066,19 @@ fun! s:NetrwListHide() let hide = listhide let listhide = "" endif -" call Decho("hide<".hide."> listhide<".listhide.'>','~'.expand("<slnum>")) +" call Decho("..extracted from listhide: hide<".hide."> g:netrw_sort_by<".g:netrw_sort_by.'>','~'.expand("<slnum>")) + if g:netrw_sort_by =~ '^[ts]' + if hide =~ '^\^' +" call Decho("..modify hide to handle a \"^...\" pattern",'~'.expand("<slnum>")) + let hide= substitute(hide,'^\^','^\(\\d\\+/\)','') + elseif hide =~ '^\\(\^' + let hide= substitute(hide,'^\\(\^','\\(^\\(\\d\\+/\\)','') + endif +" call Decho("..hide<".hide."> listhide<".listhide.'>','~'.expand("<slnum>")) + endif " Prune the list by hiding any files which match -" call Decho("prune the list by hiding any files which ",((g:netrw_hide == 1)? "" : "don't")." match hide<".hide.">") +" call Decho("..prune the list by hiding any files which ",((g:netrw_hide == 1)? "" : "don't")." match hide<".hide.">") if g:netrw_hide == 1 " call Decho("..hiding<".hide.">",'~'.expand("<slnum>")) exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d' @@ -6089,7 +6099,6 @@ fun! s:NetrwListHide() " remove any blank lines that have somehow remained. " This seems to happen under Windows. exe 'sil! NetrwKeepj 1,$g@^\s*$@d' -" call DechoBuf(bufnr("%"),"COMBAK#4") let @@= ykeep " call Dret("s:NetrwListHide") @@ -6297,7 +6306,7 @@ fun! s:NetrwMaps(islocal) " generate default <Plug> maps {{{3 if !hasmapto('<Plug>NetrwHide') |nmap <buffer> <silent> <nowait> a <Plug>NetrwHide_a|endif - if !hasmapto('<Plug>NetrwBrowseUpDir') |nmap <buffer> <silent> <nowait> - <Plug>NetrwBrowseUpDir |endif + if !hasmapto('<Plug>NetrwBrowseUpDir') |nmap <buffer> <silent> <nowait> - <Plug>NetrwBrowseUpDir|endif if !hasmapto('<Plug>NetrwOpenFile') |nmap <buffer> <silent> <nowait> % <Plug>NetrwOpenFile|endif if !hasmapto('<Plug>NetrwBadd_cb') |nmap <buffer> <silent> <nowait> cb <Plug>NetrwBadd_cb|endif if !hasmapto('<Plug>NetrwBadd_cB') |nmap <buffer> <silent> <nowait> cB <Plug>NetrwBadd_cB|endif @@ -6614,9 +6623,9 @@ fun! s:NetrwMarkFiles(islocal,...) while i <= a:0 if a:islocal if v:version > 704 || (v:version == 704 && has("patch656")) - let mffiles= glob(fnameescape(a:{i}),0,1,1) + let mffiles= glob(a:{i},0,1,1) else - let mffiles= glob(fnameescape(a:{i}),0,1) + let mffiles= glob(a:{i},0,1) endif else let mffiles= [a:{i}] @@ -6757,13 +6766,15 @@ fun! s:NetrwMarkFile(islocal,fname) if index(s:netrwmarkfilelist,dname) == -1 " append new filename to global markfilelist call add(s:netrwmarkfilelist,s:ComposePath(b:netrw_curdir,a:fname)) -" call Decho("append filename<".a:fname."> to global markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) +" call Decho("append filename<".a:fname."> to global s:markfilelist<".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) else " remove new filename from global markfilelist -" call Decho("filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand("<slnum>")) +" call Decho("remove new filename from global s:markfilelist",'~'.expand("<slnum>")) +" call Decho("..filter(".string(s:netrwmarkfilelist).",'v:val != '.".dname.")",'~'.expand("<slnum>")) call filter(s:netrwmarkfilelist,'v:val != "'.dname.'"') -" call Decho("ending s:netrwmarkfilelist <".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) +" call Decho("..ending s:netrwmarkfilelist <".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) if s:netrwmarkfilelist == [] +" call Decho("s:netrwmarkfilelist is empty; unlet it",'~'.expand("<slnum>")) unlet s:netrwmarkfilelist endif endif @@ -6787,7 +6798,8 @@ fun! s:NetrwMarkFile(islocal,fname) endif endif let @@= ykeep -" call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist").">") +" call Decho("s:netrwmarkfilelist[".(exists("s:netrwmarkfilelist")? string(s:netrwmarkfilelist) : "")."] (avail in all buffers)",'~'.expand("<slnum>")) +" call Dret("s:NetrwMarkFile : s:netrwmarkfilelist_".curbufnr."<".(exists("s:netrwmarkfilelist_{curbufnr}")? string(s:netrwmarkfilelist_{curbufnr}) : " doesn't exist")."> (buf#".curbufnr."list)") endfun " --------------------------------------------------------------------- @@ -7078,17 +7090,8 @@ fun! s:NetrwMarkFileCopy(islocal,...) " call Dret("s:NetrwMarkFileCopy : lcd failure") return endif - if v:version < 704 || (v:version == 704 && !has("patch1107")) - call s:NetrwExe("sil !".g:netrw_localrmdir.g:netrw_localrmdiropt." ".s:ShellEscape(tmpdir,1)) - if v:shell_error != 0 - call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localrmdir<".g:netrw_localrmdir."> to something that works",80) -" " call Dret("s:NetrwMarkFileCopy : failed: sil !".g:netrw_localrmdir." ".s:ShellEscape(tmpdir,1) ) - return - endif - else - if delete(tmpdir,"d") - call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103) - endif + if delete(tmpdir,"d") + call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".tmpdir.">!",103) endif else if s:NetrwLcd(curdir) @@ -7542,8 +7545,9 @@ fun! s:NetrwMarkFileGrep(islocal) let curdir = s:NetrwGetCurdir(a:islocal) if exists("s:netrwmarkfilelist") -" call Decho("s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) +" call Decho("using s:netrwmarkfilelist".string(s:netrwmarkfilelist).">",'~'.expand("<slnum>")) let netrwmarkfilelist= join(map(deepcopy(s:netrwmarkfilelist), "fnameescape(v:val)")) +" call Decho("keeping copy of s:netrwmarkfilelist in function-local variable,'~'.expand("<slnum>"))" call s:NetrwUnmarkAll() else " call Decho('no marked files, using "*"','~'.expand("<slnum>")) @@ -7551,6 +7555,7 @@ fun! s:NetrwMarkFileGrep(islocal) endif " ask user for pattern +" call Decho("ask user for search pattern",'~'.expand("<slnum>")) call inputsave() let pat= input("Enter pattern: ","") call inputrestore() @@ -8663,7 +8668,7 @@ fun! s:NetrwUpload(fname,tgt,...) " -i : turns off interactive prompting from ftp " -n unix : DON'T use <.netrc>, even though it exists " -n win32: quit being obnoxious about password - NetrwKeepj norm! 1Gdd + NetrwKeepj norm! 1G"_dd call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options) " If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar) sil NetrwKeepj g/Local directory now/d @@ -9501,15 +9506,19 @@ fun! s:NetrwWideListing() " fpl: filenames per line " fpc: filenames per column setl ma noro + let keepa= @a " call Decho("setl ma noro",'~'.expand("<slnum>")) let b:netrw_cpf= 0 if line("$") >= w:netrw_bannercnt + " determine the maximum filename size; use that to set cpf exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif' NetrwKeepj call histdel("/",-1) else + let @a= keepa " call Dret("NetrwWideListing") return endif + " allow for two spaces to separate columns let b:netrw_cpf= b:netrw_cpf + 2 " call Decho("b:netrw_cpf=max_filename_length+2=".b:netrw_cpf,'~'.expand("<slnum>")) @@ -9532,10 +9541,11 @@ fun! s:NetrwWideListing() if newcolend > line("$") | let newcolend= line("$") | endif let newcolqty= newcolend - newcolstart exe newcolstart + " COMBAK: both of the visual-mode using lines below are problematic vis-a-vis @* if newcolqty == 0 - exe "sil! NetrwKeepj norm! 0\<c-v>$hx".w:netrw_bannercnt."G$p" + exe "sil! NetrwKeepj norm! 0\<c-v>$h\"ax".w:netrw_bannercnt."G$\"ap" else - exe "sil! NetrwKeepj norm! 0\<c-v>".newcolqty.'j$hx'.w:netrw_bannercnt.'G$p' + exe "sil! NetrwKeepj norm! 0\<c-v>".newcolqty.'j$h"ax'.w:netrw_bannercnt.'G$"ap' endif exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _' exe 'sil! NetrwKeepj '.w:netrw_bannercnt @@ -9546,6 +9556,7 @@ fun! s:NetrwWideListing() exe 'nno <buffer> <silent> b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\<cr>" " call Decho("NetrwWideListing) setl noma nomod ro",'~'.expand("<slnum>")) exe "setl ".g:netrw_bufsettings + let @a= keepa " call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("<slnum>")) " call Dret("NetrwWideListing") return @@ -9743,6 +9754,7 @@ fun! s:PerformListing(islocal) exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options endif endif + " remove priority pattern prefix " call Decho("remove priority pattern prefix",'~'.expand("<slnum>")) exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e' @@ -9781,6 +9793,7 @@ fun! s:PerformListing(islocal) " call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!','~'.expand("<slnum>")) exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options endif +" call Decho("remove leading digits/ (sorting) information from listing",'~'.expand("<slnum>")) exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e' NetrwKeepj call histdel("/",-1) endif @@ -9843,6 +9856,7 @@ fun! s:PerformListing(islocal) " call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1),'~'.expand("<slnum>")) exe "setl ts=".(g:netrw_maxfilenamelen+1) endif +" call Decho("PerformListing buffer:",'~'.expand("<slnum>")) " call DechoBuf(bufnr("%")) if exists("s:treecurpos") @@ -10810,7 +10824,6 @@ fun! s:LocalListing() for filename in filelist " call Decho(" ",'~'.expand("<slnum>")) " call Decho("for filename in filelist: filename<".filename.">",'~'.expand("<slnum>")) -" call DechoBuf(bufnr("%"),"COMBAK#1") if getftype(filename) == "link" " indicate a symbolic link @@ -10868,10 +10881,10 @@ fun! s:LocalListing() if w:netrw_liststyle == s:LONGLIST let sz = getfsize(filename) + let fsz = strpart(" ",1,15-strlen(sz)).sz if g:netrw_sizestyle =~# "[hH]" let sz= s:NetrwHumanReadable(sz) endif - let fsz = strpart(" ",1,15-strlen(sz)).sz let longfile= printf("%-".(g:netrw_maxfilenamelen+1)."s",pfile) let pfile = longfile.fsz." ".strftime(g:netrw_timefmt,getftime(filename)) " call Decho("longlist support: sz=".sz." fsz=".fsz,'~'.expand("<slnum>")) @@ -10879,10 +10892,11 @@ fun! s:LocalListing() if g:netrw_sort_by =~# "^t" " sort by time (handles time up to 1 quintillion seconds, US) + " Decorate listing by prepending a timestamp/ . Sorting will then be done based on time. " call Decho("getftime(".filename.")=".getftime(filename),'~'.expand("<slnum>")) let t = getftime(filename) let ft = strpart("000000000000000000",1,18-strlen(t)).t -" call Decho("exe NetrwKeepj put ='".ft.'/'.filename."'",'~'.expand("<slnum>")) +" call Decho("exe NetrwKeepj put ='".ft.'/'.pfile."'",'~'.expand("<slnum>")) let ftpfile= ft.'/'.pfile sil! NetrwKeepj put=ftpfile @@ -10903,7 +10917,7 @@ fun! s:LocalListing() " call Decho("exe NetrwKeepj put ='".pfile."'",'~'.expand("<slnum>")) sil! NetrwKeepj put=pfile endif -" call DechoBuf(bufnr("%"),"COMBAK#2") +" call DechoBuf(bufnr("%"),"bufnr(%)") endfor " cleanup any windows mess at end-of-line @@ -10950,9 +10964,10 @@ fun! s:NetrwLocalRename(path) range " call Dfunc("NetrwLocalRename(path<".a:path.">)") " preparation for removing multiple files/directories - let ykeep = @@ - let ctr = a:firstline - let svpos = winsaveview() + let ykeep = @@ + let ctr = a:firstline + let svpos = winsaveview() + let all = 0 " call Decho("saving posn to svpos<".string(svpos).">",'~'.expand("<slnum>")) " rename files given by the markfilelist @@ -10980,6 +10995,23 @@ fun! s:NetrwLocalRename(path) range let newname = substitute(oldname,subfrom,subto,'') endif endif + if !all && filereadable(newname) + call inputsave() + let response= input("File<".newname."> already exists; do you want to overwrite it? (y/all/n) ") + call inputrestore() + if response == "all" + let all= 1 + elseif response != "y" && response != "yes" + " refresh the directory +" call Decho("refresh the directory listing",'~'.expand("<slnum>")) + NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./')) +" call Decho("restoring posn to svpos<".string(svpos).">",'~'.expand("<slnum>")) + NetrwKeepj call winrestview(svpos) + let @@= ykeep +" call Dret("NetrwLocalRename") + return + endif + endif call rename(oldname,newname) endfor call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir) @@ -11003,14 +11035,14 @@ fun! s:NetrwLocalRename(path) range NetrwKeepj norm! 0 let oldname= s:ComposePath(a:path,curword) -" call Decho("oldname<".oldname.">",'~'.expand("<slnum>")) +" call Decho("oldname<".oldname.">",'~'.expand("<slnum>")) call inputsave() let newname= input("Moving ".oldname." to : ",substitute(oldname,'/*$','','e')) call inputrestore() call rename(oldname,newname) -" call Decho("renaming <".oldname."> to <".newname.">",'~'.expand("<slnum>")) +" call Decho("renaming <".oldname."> to <".newname.">",'~'.expand("<slnum>")) let ctr= ctr + 1 endwhile @@ -11220,7 +11252,9 @@ fun! netrw#Expose(varname) " call Dfunc("netrw#Expose(varname<".a:varname.">)") if exists("s:".a:varname) exe "let retval= s:".a:varname +" call Decho("retval=".retval,'~'.expand("<slnum>")) if exists("g:netrw_pchk") +" call Decho("type(g:netrw_pchk=".g:netrw_pchk.")=".type(retval),'~'.expand("<slnum>")) if type(retval) == 3 let retval = copy(retval) let i = 0 @@ -11229,10 +11263,13 @@ fun! netrw#Expose(varname) let i = i + 1 endwhile endif -" call Dret("netrw#Expose ".string(retval)) +" call Dret("netrw#Expose ".string(retval)),'~'.expand("<slnum>")) return string(retval) + else +" call Decho("g:netrw_pchk doesn't exist",'~'.expand("<slnum>")) endif else +" call Decho("s:".a:varname." doesn't exist",'~'.expand("<slnum>")) let retval= "n/a" endif @@ -11793,6 +11830,9 @@ fun! s:NetrwExe(cmd) " call Decho("exe ".a:cmd,'~'.expand("<slnum>")) exe a:cmd endif + if v:shell_error + call netrw#ErrorMsg(s:WARNING,"shell signalled an error",106) + endif " call Dret("s:NetrwExe : v:shell_error=".v:shell_error) endfun diff --git a/runtime/autoload/netrwSettings.vim b/runtime/autoload/netrwSettings.vim index 327db6a540..bed5cfc455 100644 --- a/runtime/autoload/netrwSettings.vim +++ b/runtime/autoload/netrwSettings.vim @@ -1,6 +1,6 @@ " netrwSettings.vim: makes netrw settings simpler " Date: Nov 09, 2016 -" Maintainer: Charles E Campbell <drchipNOSPAM at campbellfamily dot biz> +" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> " Version: 16 " Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, diff --git a/runtime/autoload/netrw_gitignore.vim b/runtime/autoload/netrw_gitignore.vim index da3f50bca0..1b55e2488a 100644 --- a/runtime/autoload/netrw_gitignore.vim +++ b/runtime/autoload/netrw_gitignore.vim @@ -18,61 +18,5 @@ " holder be liable for any damages resulting from the use " of this software. function! netrw_gitignore#Hide(...) - let additional_files = a:000 - - let default_files = ['.gitignore', '.git/info/exclude'] - - " get existing global/system gitignore files - let global_gitignore = expand(substitute(system("git config --global core.excludesfile"), '\n', '', 'g')) - if global_gitignore !=# '' - let default_files = add(default_files, global_gitignore) - endif - let system_gitignore = expand(substitute(system("git config --system core.excludesfile"), '\n', '', 'g')) - if system_gitignore !=# '' - let default_files = add(default_files, system_gitignore) - endif - - " append additional files if given as function arguments - if additional_files !=# [] - let files = extend(default_files, additional_files) - else - let files = default_files - endif - - " keep only existing/readable files - let gitignore_files = [] - for file in files - if filereadable(file) - let gitignore_files = add(gitignore_files, file) - endif - endfor - - " get contents of gitignore patterns from those files - let gitignore_lines = [] - for file in gitignore_files - for line in readfile(file) - " filter empty lines and comments - if line !~# '^#' && line !~# '^$' - let gitignore_lines = add(gitignore_lines, line) - endif - endfor - endfor - - " convert gitignore patterns to Netrw/Vim regex patterns - let escaped_lines = [] - for line in gitignore_lines - let escaped = line - let escaped = substitute(escaped, '\*\*', '*', 'g') - let escaped = substitute(escaped, '\.', '\\.', 'g') - let escaped = substitute(escaped, '\$', '\\$', 'g') - let escaped = substitute(escaped, '*', '.*', 'g') - " correction: dot, dollar and asterisks chars shouldn't be escaped when - " within regex matching groups. - let escaped = substitute(escaped, '\(\[[^]]*\)\zs\\\.', '\.', 'g') - let escaped = substitute(escaped, '\(\[[^]]*\)\zs\\\$', '\$', 'g') - let escaped = substitute(escaped, '\(\[[^]]*\)\zs\.\*', '*', 'g') - let escaped_lines = add(escaped_lines, escaped) - endfor - - return join(escaped_lines, ',') + return substitute(substitute(system('git ls-files --other --ignored --exclude-standard --directory'), '\n', ',', 'g'), ',$', '', '') endfunction diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index c2195fa02d..07f37d604f 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -132,6 +132,12 @@ function! provider#clipboard#Executable() abort let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'win32yank' + elseif executable('termux-clipboard-set') + let s:copy['+'] = ['termux-clipboard-set'] + let s:paste['+'] = ['termux-clipboard-get'] + let s:copy['*'] = s:copy['+'] + let s:paste['*'] = s:paste['+'] + return 'termux-clipboard' elseif !empty($TMUX) && executable('tmux') let s:copy['+'] = ['tmux', 'load-buffer', '-'] let s:paste['+'] = ['tmux', 'save-buffer', '-'] diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index dc670dbd14..b6c4c660b8 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -1,13 +1,13 @@ " tar.vim: Handles browsing tarfiles " AUTOLOAD PORTION -" Date: Apr 17, 2013 -" Version: 29 -" Maintainer: Charles E Campbell <NdrOchip@ScampbellPfamily.AbizM-NOSPAM> -" License: Vim License (see vim's :help license) +" Date: Jan 07, 2020 +" Version: 32 +" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> +" License: Vim License (see vim's :help license) " " Contains many ideas from Michael Toren's <tar.vim> " -" Copyright: Copyright (C) 2005-2011 Charles E. Campbell {{{1 +" Copyright: Copyright (C) 2005-2017 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright " notice is copied with it. Like anything else that's free, @@ -22,7 +22,7 @@ if &cp || exists("g:loaded_tar") finish endif -let g:loaded_tar= "v29" +let g:loaded_tar= "v32" if v:version < 702 echohl WarningMsg echo "***warning*** this version of tar needs vim 7.2" @@ -48,6 +48,9 @@ endif if !exists("g:tar_writeoptions") let g:tar_writeoptions= "uf" endif +if !exists("g:tar_delfile") + let g:tar_delfile="--delete -f" +endif if !exists("g:netrw_cygwin") if has("win32") || has("win95") || has("win64") || has("win16") if &shell =~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$' @@ -109,6 +112,7 @@ fun! tar#Browse(tarfile) " sanity checks if !executable(g:tar_cmd) redraw! +" call Decho('***error*** (tar#Browse) "'.g:tar_cmd.'" not available on your system') echohl Error | echo '***error*** (tar#Browse) "'.g:tar_cmd.'" not available on your system' let &report= repkeep " call Dret("tar#Browse") @@ -119,6 +123,7 @@ fun! tar#Browse(tarfile) if a:tarfile !~# '^\a\+://' " if it's an url, don't complain, let url-handlers such as vim do its thing redraw! +" call Decho("***error*** (tar#Browse) File not readable<".a:tarfile.">") echohl Error | echo "***error*** (tar#Browse) File not readable<".a:tarfile.">" | echohl None endif let &report= repkeep @@ -152,12 +157,29 @@ fun! tar#Browse(tarfile) " assuming cygwin let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e') endif - let curlast= line("$") - if tarfile =~# '\.\(gz\|tgz\)$' - let gzip_command = s:get_gzip_command(tarfile) + + if tarfile =~# '\.\(gz\)$' " call Decho("1: exe silent r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - ") - exe "sil! r! " . gzip_command . " -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + + elseif tarfile =~# '\.\(tgz\)$' || tarfile =~# '\.\(tbz\)$' || tarfile =~# '\.\(txz\)$' || tarfile =~# '\.\(tzs\)$' + if has("unix") && executable("file") + let filekind= system("file ".shellescape(tarfile,1)) =~ "bzip2" + else + let filekind= "" + endif + + if filekind =~ "bzip2" + exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + elseif filekind =~ "XZ" + exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + elseif filekind =~ "Zstandard" + exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + else + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + endif + elseif tarfile =~# '\.lrp' " call Decho("2: exe silent r! cat -- ".shellescape(tarfile,1)."|gzip -d -c -|".g:tar_cmd." -".g:tar_browseoptions." - ") exe "sil! r! cat -- ".shellescape(tarfile,1)."|gzip -d -c -|".g:tar_cmd." -".g:tar_browseoptions." - " @@ -170,6 +192,8 @@ fun! tar#Browse(tarfile) elseif tarfile =~# '\.\(xz\|txz\)$' " call Decho("3: exe silent r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - ") exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " + elseif tarfile =~# '\.\(zst\|tzs\)$' + exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " else if tarfile =~ '^\s*-' " A file name starting with a dash is taken as an option. Prepend ./ to avoid that. @@ -184,7 +208,7 @@ fun! tar#Browse(tarfile) " call Dret("tar#Browse : a:tarfile<".a:tarfile.">") return endif - if line("$") == curlast || ( line("$") == (curlast + 1) && getline("$") =~ '\c\%(warning\|error\|inappropriate\|unrecognized\)') + if line("$") == curlast || ( line("$") == (curlast + 1) && getline("$") =~# '\c\%(warning\|error\|inappropriate\|unrecognized\)') redraw! echohl WarningMsg | echo "***warning*** (tar#Browse) ".a:tarfile." doesn't appear to be a tar file" | echohl None keepj sil! %d @@ -197,8 +221,13 @@ fun! tar#Browse(tarfile) return endif + " set up maps supported for tar setlocal noma nomod ro - noremap <silent> <buffer> <cr> :call <SID>TarBrowseSelect()<cr> + noremap <silent> <buffer> <cr> :call <SID>TarBrowseSelect()<cr> + noremap <silent> <buffer> x :call tar#Extract()<cr> + if &mouse != "" + noremap <silent> <buffer> <leftmouse> <leftmouse>:call <SID>TarBrowseSelect()<cr> + endif let &report= repkeep " call Dret("tar#Browse : b:tarfile<".b:tarfile.">") @@ -235,7 +264,8 @@ fun! s:TarBrowseSelect() let tarfile=substitute(system("cygpath -u ".shellescape(tarfile,0)),'\n$','','e') endif - new + " open a new window (tar#Read will read a file into it) + noswapfile new if !exists("g:tar_nomax") || g:tar_nomax == 0 wincmd _ endif @@ -267,7 +297,7 @@ fun! tar#Read(fname,mode) if fname =~ '\.bz2$' && executable("bzcat") let decmp= "|bzcat" let doro = 1 - elseif fname =~ '\.gz$' && executable("zcat") + elseif fname =~ '\.t\=gz$' && executable("zcat") let decmp= "|zcat" let doro = 1 elseif fname =~ '\.lzma$' && executable("lzcat") @@ -276,6 +306,9 @@ fun! tar#Read(fname,mode) elseif fname =~ '\.xz$' && executable("xzcat") let decmp= "|xzcat" let doro = 1 + elseif fname =~ '\.zst$' && executable("zstdcat") + let decmp= "|zstdcat" + let doro = 1 else let decmp="" let doro = 0 @@ -291,20 +324,31 @@ fun! tar#Read(fname,mode) endif if tarfile =~# '\.bz2$' -" call Decho("7: exe silent r! bzip2 -d -c ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp) exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp - elseif tarfile =~# '\.\(gz\|tgz\)$' - let gzip_command = s:get_gzip_command(tarfile) -" call Decho("5: exe silent r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd.' -'.g:tar_readoptions.' - '.tar_secure.shellescape(fname,1)) - exe "sil! r! " . gzip_command . " -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + elseif tarfile =~# '\.\(gz\)$' + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + + elseif tarfile =~# '\(\.tgz\|\.tbz\|\.txz\)' + if has("unix") && executable("file") + let filekind= system("file ".shellescape(tarfile,1)) + else + let filekind= "" + endif + if filekind =~ "bzip2" + exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + elseif filekind =~ "XZ" + exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + elseif filekind =~ "Zstandard" + exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + else + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + endif + elseif tarfile =~# '\.lrp$' -" call Decho("6: exe silent r! cat ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp) exe "sil! r! cat -- ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp elseif tarfile =~# '\.lzma$' -" call Decho("7: exe silent r! lzma -d -c ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp) exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp elseif tarfile =~# '\.\(xz\|txz\)$' -" call Decho("3: exe silent r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp) exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp else if tarfile =~ '^\s*-' @@ -348,13 +392,14 @@ fun! tar#Write(fname) " sanity checks if !executable(g:tar_cmd) redraw! - echohl Error | echo '***error*** (tar#Browse) "'.g:tar_cmd.'" not available on your system' +" call Decho('***error*** (tar#Browse) "'.g:tar_cmd.'" not available on your system') let &report= repkeep " call Dret("tar#Write") return endif if !exists("*mkdir") redraw! +" call Decho("***error*** (tar#Write) sorry, mkdir() doesn't work on your system") echohl Error | echo "***error*** (tar#Write) sorry, mkdir() doesn't work on your system" | echohl None let &report= repkeep " call Dret("tar#Write") @@ -375,6 +420,7 @@ fun! tar#Write(fname) exe "cd ".fnameescape(tmpdir) catch /^Vim\%((\a\+)\)\=:E344/ redraw! +" call Decho("***error*** (tar#Write) cannot cd to temporary directory") echohl Error | echo "***error*** (tar#Write) cannot cd to temporary directory" | Echohl None let &report= repkeep " call Dret("tar#Write") @@ -393,8 +439,6 @@ fun! tar#Write(fname) let tarfile = substitute(b:tarfile,'tarfile:\(.\{-}\)::.*$','\1','') let fname = substitute(b:tarfile,'tarfile:.\{-}::\(.*\)$','\1','') - let gzip_command = s:get_gzip_command(tarfile) - " handle compressed archives if tarfile =~# '\.bz2' call system("bzip2 -d -- ".shellescape(tarfile,0)) @@ -402,12 +446,12 @@ fun! tar#Write(fname) let compress= "bzip2 -- ".shellescape(tarfile,0) " call Decho("compress<".compress.">") elseif tarfile =~# '\.gz' - call system(gzip_command . " -d -- ".shellescape(tarfile,0)) + call system("gzip -d -- ".shellescape(tarfile,0)) let tarfile = substitute(tarfile,'\.gz','','e') let compress= "gzip -- ".shellescape(tarfile,0) " call Decho("compress<".compress.">") elseif tarfile =~# '\.tgz' - call system(gzip_command . " -d -- ".shellescape(tarfile,0)) + call system("gzip -d -- ".shellescape(tarfile,0)) let tarfile = substitute(tarfile,'\.tgz','.tar','e') let compress= "gzip -- ".shellescape(tarfile,0) let tgz = 1 @@ -417,6 +461,10 @@ fun! tar#Write(fname) let tarfile = substitute(tarfile,'\.xz','','e') let compress= "xz -- ".shellescape(tarfile,0) " call Decho("compress<".compress.">") + elseif tarfile =~# '\.zst' + call system("zstd --decompress -- ".shellescape(tarfile,0)) + let tarfile = substitute(tarfile,'\.zst','','e') + let compress= "zstd -- ".shellescape(tarfile,0) elseif tarfile =~# '\.lzma' call system("lzma -d -- ".shellescape(tarfile,0)) let tarfile = substitute(tarfile,'\.lzma','','e') @@ -427,6 +475,7 @@ fun! tar#Write(fname) if v:shell_error != 0 redraw! +" call Decho("***error*** (tar#Write) sorry, unable to update ".tarfile." with ".fname) echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".tarfile." with ".fname | echohl None else @@ -459,10 +508,11 @@ fun! tar#Write(fname) endif " delete old file from tarfile -" call Decho("system(".g:tar_cmd." --delete -f ".shellescape(tarfile,0)." -- ".shellescape(fname,0).")") - call system(g:tar_cmd." --delete -f ".shellescape(tarfile,0).tar_secure.shellescape(fname,0)) +" call Decho("system(".g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0)." -- ".shellescape(fname,0).")") + call system(g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0).tar_secure.shellescape(fname,0)) if v:shell_error != 0 redraw! +" call Decho("***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname)) echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname) | echohl None else @@ -471,6 +521,7 @@ fun! tar#Write(fname) call system(g:tar_cmd." -".g:tar_writeoptions." ".shellescape(tarfile,0).tar_secure.shellescape(fname,0)) if v:shell_error != 0 redraw! +" call Decho("***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname)) echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname) | echohl None elseif exists("compress") " call Decho("call system(".compress.")") @@ -486,11 +537,11 @@ fun! tar#Write(fname) if s:tblfile_{winnr()} =~ '^\a\+://' " call Decho("handle writing <".tarfile."> across network to <".s:tblfile_{winnr()}.">") let tblfile= s:tblfile_{winnr()} - 1split|enew + 1split|noswapfile enew let binkeep= &l:binary let eikeep = &ei set binary ei=all - exe "e! ".fnameescape(tarfile) + exe "noswapfile e! ".fnameescape(tarfile) call netrw#NetWrite(tblfile) let &ei = eikeep let &l:binary = binkeep @@ -524,7 +575,7 @@ fun! tar#Diff(userfname,fname) " sets up b:tardiff_otherbuf variables so each buffer knows about the other (for closing purposes) diffthis wincmd v - exe "e ".fnameescape(fname) + exe "noswapfile e ".fnameescape(fname) diffthis else redraw! @@ -534,6 +585,141 @@ fun! tar#Diff(userfname,fname) endfun " --------------------------------------------------------------------- +" tar#Extract: extract a file from a (possibly compressed) tar archive {{{2 +fun! tar#Extract() +" call Dfunc("tar#Extract()") + + let repkeep= &report + set report=10 + let fname= getline(".") +" call Decho("fname<".fname.">") + + if !exists("g:tar_secure") && fname =~ '^\s*-\|\s\+-' + redraw! + echohl WarningMsg | echo '***warning*** (tar#BrowseSelect) rejecting tarfile member<'.fname.'> because of embedded "-"' +" call Dret('tar#BrowseSelect : rejecting tarfile member<'.fname.'> because of embedded "-"') + return + endif + + " sanity check + if fname =~ '^"' + let &report= repkeep +" call Dret("TarBrowseSelect") + return + endif + + let tarball = expand("%") +" call Decho("tarball<".tarball.">") + let tarbase = substitute(tarball,'\..*$','','') +" call Decho("tarbase<".tarbase.">") + + let extractcmd= netrw#WinPath(g:tar_extractcmd) + if filereadable(tarbase.".tar") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".tar ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tgz") + let extractcmd= substitute(extractcmd,"-","-z","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tgz ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tgz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tgz ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd."t ".tarbase.".tgz ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tar.gz") + let extractcmd= substitute(extractcmd,"-","-z","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tar.gz ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tar.gz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.gz ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".tar.gz ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tbz") + let extractcmd= substitute(extractcmd,"-","-j","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tbz ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tbz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd."j ".tarbase.".tbz ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd."j ".tarbase.".tbz ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tar.bz2") + let extractcmd= substitute(extractcmd,"-","-j","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tar.bz2 ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tar.bz2 ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd."j ".tarbase.".tar.bz2 ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd."j ".tarbase.".tar.bz2 ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".txz") + let extractcmd= substitute(extractcmd,"-","-J","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".txz ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".txz ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tar.xz") + let extractcmd= substitute(extractcmd,"-","-J","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.xz ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".tar.xz ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tzs") + let extractcmd= substitute(extractcmd,"-","--zstd","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tzs ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tzs ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".tzs ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + + elseif filereadable(tarbase.".tar.zst") + let extractcmd= substitute(extractcmd,"-","--zstd","") +" call Decho("system(".extractcmd." ".shellescape(tarbase).".tar.zst ".shellescape(fname).")") + call system(extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname)) + if v:shell_error != 0 + echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.zst ".fname.": failed!" | echohl NONE +" call Decho("***error*** ".extractcmd." ".tarbase.".tar.zst ".fname.": failed!") + else + echo "***note*** successfully extracted ".fname + endif + endif + + " restore option + let &report= repkeep + +" call Dret("tar#Extract") +endfun + +" --------------------------------------------------------------------- " s:Rmdir: {{{2 fun! s:Rmdir(fname) " call Dfunc("Rmdir(fname<".a:fname.">)") @@ -587,10 +773,7 @@ fun! tar#Vimuntar(...) " if necessary, decompress the tarball; then, extract it if tartail =~ '\.tgz' - let gzip_command = s:get_gzip_command(tarfile) - if executable(gzip_command) - silent exe "!" . gzip_command . " -d ".shellescape(tartail) - elseif executable("gunzip") + if executable("gunzip") silent exe "!gunzip ".shellescape(tartail) elseif executable("gzip") silent exe "!gzip -d ".shellescape(tartail) @@ -628,28 +811,6 @@ fun! tar#Vimuntar(...) " call Dret("tar#Vimuntar") endfun -func s:get_gzip_command(file) - " Try using the "file" command to get the actual compression type, since - " there is no standard way for the naming: ".tgz", ".tbz", ".txz", etc. - " If the "file" command doesn't work fall back to just using the file name. - if a:file =~# 'z$' - let filetype = system('file ' . a:file) - if filetype =~ 'bzip2 compressed' && executable('bzip2') - return 'bzip2' - endif - if filetype =~ 'XZ compressed' && executable('xz') - return 'xz' - endif - endif - if a:file =~# 'bz2$' - return 'bzip2' - endif - if a:file =~# 'xz$' - return 'xz' - endif - return 'gzip' -endfunc - " ===================================================================== " Modelines And Restoration: {{{1 let &cpo= s:keepcpo diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 6afe64de84..abf5c5e2c8 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -104,6 +104,10 @@ function! tutor#CheckLine(line) if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') let bufn = bufnr('%') let ctext = getline(a:line) + let signs = sign_getplaced('.', {'lnum': a:line})[0].signs + if !empty(signs) + call sign_unplace('', {'id': signs[0].id}) + endif if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)] exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn else 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/delmenu.vim b/runtime/delmenu.vim index 81df87d346..5c20290152 100644 --- a/runtime/delmenu.vim +++ b/runtime/delmenu.vim @@ -2,24 +2,30 @@ " Warning: This also deletes all menus defined by the user! " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2001 May 27 +" Last Change: 2019 Dec 10 aunmenu * -silent! unlet did_install_default_menus -silent! unlet did_install_syntax_menu -if exists("did_menu_trans") +unlet! g:did_install_default_menus +unlet! g:did_install_syntax_menu + +if exists('g:did_menu_trans') menutrans clear - unlet did_menu_trans + unlet g:did_menu_trans endif -silent! unlet find_help_dialog +unlet! g:find_help_dialog -silent! unlet menutrans_help_dialog -silent! unlet menutrans_path_dialog -silent! unlet menutrans_tags_dialog -silent! unlet menutrans_textwidth_dialog -silent! unlet menutrans_fileformat_dialog -silent! unlet menutrans_no_file +unlet! g:menutrans_fileformat_choices +unlet! g:menutrans_fileformat_dialog +unlet! g:menutrans_help_dialog +unlet! g:menutrans_no_file +unlet! g:menutrans_path_dialog +unlet! g:menutrans_set_lang_to +unlet! g:menutrans_spell_add_ARG_to_word_list +unlet! g:menutrans_spell_change_ARG_to +unlet! g:menutrans_spell_ignore_ARG +unlet! g:menutrans_tags_dialog +unlet! g:menutrans_textwidth_dialog " vim: set sw=2 : diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 717a7caadf..0c17fa1669 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -538,6 +538,21 @@ nvim__screenshot({path}) *nvim__screenshot()* Attributes: ~ {fast} +nvim__set_hl_ns({ns_id}) *nvim__set_hl_ns()* + Set active namespace for highlights. + + NB: this function can be called from async contexts, but the + semantics are not yet well-defined. To start with + |nvim_set_decoration_provider| on_win and on_line callbacks + are explicitly allowed to change the namespace during a redraw + cycle. + + Attributes: ~ + {fast} + + Parameters: ~ + {ns_id} the namespace to activate + nvim__stats() *nvim__stats()* Gets internal stats. @@ -599,6 +614,22 @@ nvim_call_function({fn}, {args}) *nvim_call_function()* Return: ~ Result of the function call +nvim_chan_send({chan}, {data}) *nvim_chan_send()* + Send data to channel `id` . For a job, it writes it to the + stdin of the process. For the stdio channel |channel-stdio|, + it writes to Nvim's stdout. For an internal terminal instance + (|nvim_open_term()|) it writes directly to terimal output. See + |channel-bytes| for more information. + + This function writes raw data, not RPC messages. If the + channel was created with `rpc=true` then the channel expects + RPC messages, use |vim.rpcnotify()| and |vim.rpcrequest()| + instead. + + Parameters: ~ + {chan} id of the channel + {data} data to write. 8-bit clean: can contain NUL bytes. + nvim_command({command}) *nvim_command()* Executes an ex-command. @@ -1148,6 +1179,39 @@ nvim_load_context({dict}) *nvim_load_context()* Parameters: ~ {dict} |Context| map. +nvim_notify({msg}, {log_level}, {opts}) *nvim_notify()* + Notify the user with a message + + Relays the call to vim.notify . By default forwards your + message in the echo area but can be overriden to trigger + desktop notifications. + + Parameters: ~ + {msg} Message to display to the user + {log_level} The log level + {opts} Reserved for future use. + +nvim_open_term({buffer}, {opts}) *nvim_open_term()* + Open a terminal instance in a buffer + + By default (and currently the only option) the terminal will + not be connected to an external process. Instead, input send + on the channel will be echoed directly by the terminal. This + is useful to disply ANSI terminal sequences returned as part + of a rpc message, or similar. + + Note: to directly initiate the terminal using the right size, + display the buffer in a configured window before calling this. + For instance, for a floating display, first create an empty + buffer using |nvim_create_buf()|, then display it using + |nvim_open_win()|, and then call this function. Then + |nvim_chan_send()| cal be called immediately to process + sequences in a virtual terminal having the intended size. + + Parameters: ~ + {buffer} the buffer to use (expected to be empty) + {opts} Optional parameters. Reserved for future use. + nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* Open a new window. @@ -1247,6 +1311,34 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* and clearing the |EndOfBuffer| region in 'winhighlight'. + • `border`: style of (optional) window border. This can + either be a string or an array. the string + values are: + • "none" No border. This is the default + • "single" a single line box + • "double" a double line box + • "shadow" a drop shadow effect by blending + with the background. If it is an array it + should be an array of eight items or any + divisor of eight. The array will specifify + the eight chars building up the border in a + clockwise fashion starting with the top-left + corner. As, an example, the double box style + could be specified as: [ "╔", "═" ,"╗", "║", + "╝", "═", "╚", "║" ] if the number of chars + are less than eight, they will be repeated. + Thus an ASCII border could be specified as: + [ "/", "-", "\\", "|" ] or all chars the + same as: [ "x" ] An empty string can be used + to turn off a specific border, for instance: + [ "", "", "", ">", "", "", "", "<" ] will + only make vertical borders but not + horizontal ones. By default `FloatBorder` + highlight is used which links to `VertSplit` + when not defined. It could also be specified + by character: [ {"+", "MyCorner"}, {"x", + "MyBorder"} ] + Return: ~ Window handle, or 0 on error @@ -1403,9 +1495,9 @@ nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()* • "c" |charwise| mode • "l" |linewise| mode • "" guess by contents, see |setreg()| - {after} Insert after cursor (like |p|), or before (like - |P|). - {follow} Place cursor at end of inserted text. + {after} If true insert after cursor (like |p|), or + before (like |P|). + {follow} If true place cursor at end of inserted text. *nvim_replace_termcodes()* nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) @@ -1611,21 +1703,6 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()* keys are also recognized: `default` : don't override existing definition, like `hi default` -nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()* - Set active namespace for highlights. - - NB: this function can be called from async contexts, but the - semantics are not yet well-defined. To start with - |nvim_set_decoration_provider| on_win and on_line callbacks - are explicitly allowed to change the namespace during a redraw - cycle. - - Attributes: ~ - {fast} - - Parameters: ~ - {ns_id} the namespace to activate - nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()* Sets a global |mapping| for the given mode. @@ -1725,7 +1802,7 @@ nvim__buf_stats({buffer}) *nvim__buf_stats()* TODO: Documentation *nvim_buf_add_highlight()* -nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_start}, +nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, {col_end}) Adds a highlight to buffer. @@ -1800,6 +1877,25 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* • deleted_codeunits (if `utf_sizes` is true) + • on_bytes: lua callback invoked on change. + This callback receives more granular + information about the change compared to + on_lines. Return`true`to detach. Args: + • the string "bytes" + • buffer handle + • b:changedtick + • start row of the changed text + (zero-indexed) + • start column of the changed text + • byte offset of the changed text (from + the start of the buffer) + • old end row of the changed text + • old end column of the changed text + • old end byte length of the changed text + • new end row of the changed text + • new end column of the changed text + • new end byte length of the changed text + • on_changedtick: Lua callback invoked on changedtick increment without text change. Args: @@ -1812,6 +1908,12 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* • the string "detach" • buffer handle + • on_reload: Lua callback invoked on + reload. The entire buffer content should + be considered changed. Args: + • the string "detach" + • buffer handle + • utf_sizes: include UTF-32 and UTF-16 size of the replaced region, as args to `on_lines` . @@ -1948,8 +2050,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 @@ -2159,6 +2260,26 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) • hl_group : name of the highlight group used to highlight this mark. • virt_text : virtual text to link to this mark. + • virt_text_pos : positioning of virtual text. + Possible values: + • "eol": right after eol character (default) + • "overlay": display over the specified + column, without shifting the underlying + text. + + • virt_text_hide : hide the virtual text when + the background text is selected or hidden due + to horizontal scroll 'nowrap' + • hl_mode : control how highlights are combined + with the highlights of the text. Currently + only affects virt_text highlights, but might + affect`hl_group`in later versions. + • "replace": only show the virt_text color. + This is the default + • "combine": combine with background text + color + • "blend": blend with background text color. + • ephemeral : for use with |nvim_set_decoration_provider| callbacks. The mark will only be used for the current redraw @@ -2173,6 +2294,9 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) exists) will be shifted in when new text is inserted (true for right, false for left). Defaults to false. + • priority: a priority value for the highlight + group. For example treesitter highlighting + uses a value of 100. Return: ~ Id of the created/updated extmark @@ -2425,6 +2549,21 @@ nvim_win_get_width({window}) *nvim_win_get_width()* Return: ~ Width as a count of columns +nvim_win_hide({window}) *nvim_win_hide()* + Closes the window and hide the buffer it contains (like + |:hide| with a |window-ID|). + + Like |:hide| the buffer becomes hidden unless another window + is editing it, or 'bufhidden' is `unload` , `delete` or `wipe` + as opposed to |:close| or |nvim_win_close|, which will close + the buffer. + + Attributes: ~ + not allowed when |textlock| is active + + Parameters: ~ + {window} Window handle, or 0 for current window + nvim_win_is_valid({window}) *nvim_win_is_valid()* Checks if a window is valid diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index f3ed086933..310d244fbc 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -445,6 +445,9 @@ SHIFTING LINES LEFT OR RIGHT *shift-left-right* *<* <{motion} Shift {motion} lines one 'shiftwidth' leftwards. + If the 'shiftwidth' option is set to zero, the amount + of indent is calculated at the first non-blank + character in the line. *<<* << Shift [count] lines one 'shiftwidth' leftwards. @@ -455,6 +458,9 @@ SHIFTING LINES LEFT OR RIGHT *shift-left-right* *>* >{motion} Shift {motion} lines one 'shiftwidth' rightwards. + If the 'shiftwidth' option is set to zero, the amount + of indent is calculated at the first non-blank + character in the line. *>>* >> Shift [count] lines one 'shiftwidth' rightwards. @@ -934,6 +940,10 @@ This replaces each 'E' character with a euro sign. Read more in |<Char->|. this (that's a good habit anyway). `:retab!` may also change a sequence of spaces by <Tab> characters, which can mess up a printf(). + A list of tab widths separated by commas may be used + in place of a single tabstop. Each value in the list + represents the width of one tabstop, except the final + value which applies to all following tabstops. *retab-example* Example for using autocommands and ":retab" to edit a file which is stored diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 967f4b26f2..656bb10c45 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -174,4 +174,81 @@ Put this in `uppercase.vim` and run: > nvim --headless --cmd "source uppercase.vim" ============================================================================== +5. Using a prompt buffer *prompt-buffer* + +If you want to type input for the job in a Vim window you have a few options: +- Use a normal buffer and handle all possible commands yourself. + This will be complicated, since there are so many possible commands. +- Use a terminal window. This works well if what you type goes directly to + the job and the job output is directly displayed in the window. + See |terminal|. +- Use a window with a prompt buffer. This works well when entering a line for + the job in Vim while displaying (possibly filtered) output from the job. + +A prompt buffer is created by setting 'buftype' to "prompt". You would +normally only do that in a newly created buffer. + +The user can edit and enter one line of text at the very last line of the +buffer. When pressing Enter in the prompt line the callback set with +|prompt_setcallback()| is invoked. It would normally send the line to a job. +Another callback would receive the output from the job and display it in the +buffer, below the prompt (and above the next prompt). + +Only the text in the last line, after the prompt, is editable. The rest of the +buffer is not modifiable with Normal mode commands. It can be modified by +calling functions, such as |append()|. Using other commands may mess up the +buffer. + +After setting 'buftype' to "prompt" Vim does not automatically start Insert +mode, use `:startinsert` if you want to enter Insert mode, so that the user +can start typing a line. + +The text of the prompt can be set with the |prompt_setprompt()| function. If +no prompt is set with |prompt_setprompt()|, "% " is used. You can get the +effective prompt text for a buffer, with |prompt_getprompt()|. + +The user can go to Normal mode and navigate through the buffer. This can be +useful to see older output or copy text. + +Any command that starts Insert mode, such as "a", "i", "A" and "I", will move +the cursor to the last line. "A" will move to the end of the line, "I" to the +start of the line. + +Here is an example for Unix. It starts a shell in the background and prompts +for the next shell command. Output from the shell is displayed above the +prompt. > + + " Function handling a line of text that has been typed. + func TextEntered(text) + " Send the text to a shell with Enter appended. + call chansend(g:shell_job, [a:text, '']) + endfunc + + " Function handling output from the shell: Added above the prompt. + func GotOutput(channel, msg, name) + call append(line("$") - 1, a:msg) + endfunc + + " Function handling the shell exit: close the window. + func JobExit(job, status, event) + quit! + endfunc + + " Start a shell in the background. + let shell_job = jobstart(["/bin/sh"], #{ + \ on_stdout: function('GotOutput'), + \ on_stderr: function('GotOutput'), + \ on_exit: function('JobExit'), + \ }) + + new + set buftype=prompt + let buf = bufnr('') + call prompt_setcallback(buf, function("TextEntered")) + call prompt_setprompt(buf, "shell command: ") + + " start accepting shell commands + startinsert +< + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 2a972483ff..3544882ceb 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -48,7 +48,7 @@ In each of the edited files these options are set: 'scrollbind' on 'cursorbind' on 'scrollopt' includes "hor" - 'wrap' off + 'wrap' off, or leave as-is if 'diffopt' includes "followwrap" 'foldmethod' "diff" 'foldcolumn' value from 'diffopt', default is 2 @@ -132,7 +132,7 @@ Otherwise they are set to their default value: 'scrollbind' off 'cursorbind' off 'scrollopt' without "hor" - 'wrap' on + 'wrap' on, or leave as-is if 'diffopt' includes "followwrap" 'foldmethod' "manual" 'foldcolumn' 0 diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index ac398ec494..aa964a521f 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -90,7 +90,7 @@ g CTRL-G Prints the current position of the cursor in five :buffers :files :ls List all the currently known file names. See - 'windows.txt' |:files| |:buffers| |:ls|. + |windows.txt| |:files| |:buffers| |:ls|. Vim will remember the full path name of a file name that you enter. In most cases when the file name is displayed only the name you typed is shown, but @@ -1190,7 +1190,7 @@ The syntax is best shown via some examples: > < Open the browser in the C:/bar directory, with the current buffer filename as default, and save the buffer under the filename chosen. -Also see the |'browsedir'| option. +Also see the 'browsedir' option. For versions of Vim where browsing is not supported, the command is executed unmodified. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 384bdd63a4..cff87b2fed 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -444,7 +444,7 @@ as a key. To avoid having to put quotes around every key the #{} form can be used. This does require the key to consist only of ASCII letters, digits, '-' and '_'. Example: > - let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3} + :let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3} Note that 333 here is the string "333". Empty keys are not possible with #{}. A value can be any expression. Using a Dictionary for a value creates a @@ -1541,7 +1541,10 @@ v:dying Normally zero. When a deadly signal is caught it's set to VimLeave autocommands will not be executed. *v:exiting* *exiting-variable* -v:exiting Exit code, or |v:null| if not exiting. |VimLeave| +v:exiting Exit code, or |v:null| before invoking the |VimLeavePre| + and |VimLeave| autocmds. See |:q|, |:x| and |:cquit|. + Example: > + :au VimLeave * echo "Exit value is " .. v:exiting *v:echospace* *echospace-variable* v:echospace Number of screen cells that can be used for an `:echo` message @@ -2044,6 +2047,7 @@ assert_inrange({lower}, {upper}, {actual} [, {msg}]) Number assert {actual} is inside the range assert_match({pat}, {text} [, {msg}]) Number assert {pat} matches {text} +assert_nobeep({cmd}) Number assert {cmd} does not cause a beep assert_notequal({exp}, {act} [, {msg}]) Number assert {exp} is not equal {act} assert_notmatch({pat}, {text} [, {msg}]) @@ -2074,6 +2078,8 @@ changenr() Number current change number chanclose({id}[, {stream}]) Number Closes a channel or one of its streams chansend({id}, {data}) Number Writes {data} to channel char2nr({expr}[, {utf8}]) Number ASCII/UTF8 value of first char in {expr} +charidx({string}, {idx} [, {countcc}]) + Number char index of byte {idx} in {string} cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches col({expr}) Number column nr of cursor or mark @@ -2303,7 +2309,6 @@ perleval({expr}) any evaluate |perl| expression pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text -prompt_addtext({buf}, {expr}) none add text to a prompt buffer prompt_setcallback({buf}, {expr}) none set prompt callback function prompt_setinterrupt({buf}, {text}) none set prompt interrupt function prompt_setprompt({buf}, {text}) none set prompt text @@ -2388,8 +2393,9 @@ sha256({string}) String SHA256 checksum of {string} shellescape({string} [, {special}]) String escape {string} for use as shell command argument -shiftwidth() Number effective value of 'shiftwidth' +shiftwidth([{col}]) Number effective value of 'shiftwidth' sign_define({name} [, {dict}]) Number define or update a sign +sign_define({list}) List define or update a list of signs sign_getdefined([{name}]) List get a list of defined signs sign_getplaced([{expr} [, {dict}]]) List get a list of placed signs @@ -2397,9 +2403,12 @@ sign_jump({id}, {group}, {expr}) Number jump to a sign sign_place({id}, {group}, {name}, {expr} [, {dict}]) Number place a sign +sign_placelist({list}) List place a list of signs sign_undefine([{name}]) Number undefine a sign +sign_undefine({list}) List undefine a list of signs sign_unplace({group} [, {dict}]) Number unplace a sign +sign_unplacelist({list}) List unplace a list of signs simplify({filename}) String simplify filename as much as possible sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} @@ -2425,7 +2434,7 @@ strcharpart({str}, {start} [, {len}]) String {len} characters of {str} at character {start} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} -strftime({format} [, {time}]) String time in specified format +strftime({format} [, {time}]) String format time with a specified format strgetchar({str}, {index}) Number get char {index} from {str} stridx({haystack}, {needle} [, {start}]) Number index of {needle} in {haystack} @@ -2434,6 +2443,8 @@ strlen({expr}) Number length of the String {expr} strpart({str}, {start} [, {len} [, {chars}]]) String {len} bytes/chars of {str} at byte {start} +strptime({format}, {timestring}) + Number Convert {timestring} to unix timestamp strridx({haystack}, {needle} [, {start}]) Number last index of {needle} in {haystack} strtrans({expr}) String translate string to make it printable @@ -2493,6 +2504,8 @@ win_gotoid({expr}) Number go to |window-ID| {expr} win_id2tabwin({expr}) List get tab and window nr from |window-ID| win_id2win({expr}) Number get window nr from |window-ID| win_screenpos({nr}) List get screen position of window {nr} +win_splitmove({nr}, {target} [, {options}]) + none move window {nr} to split of {target} winbufnr({nr}) Number buffer number of window {nr} wincol() Number window column of the cursor winheight({nr}) Number height of window {nr} @@ -2630,7 +2643,8 @@ argv([{nr} [, {winid}]) assert_beeps({cmd}) *assert_beeps()* Run {cmd} and add an error message to |v:errors| if it does NOT produce a beep or visual bell. - Also see |assert_fails()| and |assert-return|. + Also see |assert_fails()|, |assert_nobeep()| and + |assert-return|. *assert_equal()* assert_equal({expected}, {actual}, [, {msg}]) @@ -2713,6 +2727,11 @@ assert_match({pattern}, {actual} [, {msg}]) < Will result in a string to be added to |v:errors|: test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~ +assert_nobeep({cmd}) *assert_nobeep()* + Run {cmd} and add an error message to |v:errors| if it + produces a beep or visual bell. + Also see |assert_beeps()|. + *assert_notequal()* assert_notequal({expected}, {actual} [, {msg}]) The opposite of `assert_equal()`: add an error message to @@ -3024,6 +3043,29 @@ char2nr({expr} [, {utf8}]) *char2nr()* A combining character is a separate character. |nr2char()| does the opposite. + *charidx()* +charidx({string}, {idx} [, {countcc}]) + Return the character index of the byte at {idx} in {string}. + The index of the first character is zero. + If there are no multibyte characters the returned value is + equal to {idx}. + When {countcc} is omitted or zero, then composing characters + are not counted separately, their byte length is added to the + preceding base character. + When {countcc} is set to 1, then composing characters are + counted as separate characters. + Returns -1 if the arguments are invalid or if {idx} is greater + than the index of the last byte in {string}. An error is + given if the first argument is not a string, the second + argument is not a number or when the third argument is present + and is not zero or one. + See |byteidx()| and |byteidxcomp()| for getting the byte index + from the character index. + Examples: > + echo charidx('áb́ć', 3) returns 1 + echo charidx('áb́ć', 6, 1) returns 4 + echo charidx('áb́ć', 16) returns -1 + cindent({lnum}) *cindent()* Get the amount of indent for line {lnum} according the C indenting rules, as with 'cindent'. @@ -4449,11 +4491,12 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* augroup autocmd groups buffer buffer names behave :behave suboptions - cmdline |cmdline-completion| + cmdline |cmdline-completion| result color color schemes command Ex command (and arguments) compiler compilers cscope |:cscope| suboptions + diff_buffer |:diffget| and |:diffput| completion dir directory names environment environment variable names event autocommand events @@ -4481,14 +4524,19 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* user user names var user variables - If {pat} is an empty string then all matches are returned. - Otherwise only items matching {pat} are returned. See - |wildcards| for the use of special characters in {pat}. + If {pat} is an empty string, then all the matches are + returned. Otherwise only items matching {pat} are returned. + See |wildcards| for the use of special characters in {pat}. If the optional {filtered} flag is set to 1, then 'wildignore' is applied to filter the results. Otherwise all the matches are returned. The 'wildignorecase' option always applies. + If {type} is "cmdline", then the |cmdline-completion| result is + returned. For example, to complete the possible values after + a ":call" command: > + echo getcompletion('call ', 'cmdline') +< If there are no matches, an empty list is returned. An invalid value for {type} produces an error. @@ -4947,7 +4995,7 @@ getwininfo([{winid}]) *getwininfo()* getwinpos([{timeout}]) *getwinpos()* The result is a list with two numbers, the result of - getwinposx() and getwinposy() combined: + |getwinposx()| and |getwinposy()| combined: [x-pos, y-pos] {timeout} can be used to specify how long to wait in msec for a response from the terminal. When omitted 100 msec is used. @@ -5091,6 +5139,8 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The iconv Can use |iconv()| for conversion. +shellslash Can use backslashes in filenames (Windows) clipboard |clipboard| provider is available. + fname_case Case in file names matters (for Darwin and MS-Windows + this is not present). mac MacOS system. nvim This is Nvim. python2 Legacy Vim |python2| interface. |has-python| @@ -5311,9 +5361,8 @@ input({opts}) prompt "" Same as {prompt} in the first form. default "" Same as {text} in the first form. completion nothing Same as {completion} in the first form. - cancelreturn "" Same as {cancelreturn} from - |inputdialog()|. Also works with - input(). + cancelreturn "" The value returned when the dialog is + cancelled. highlight nothing Highlight handler: |Funcref|. The highlighting set with |:echohl| is used for the prompt. @@ -5513,13 +5562,14 @@ id({expr}) *id()* Returns a |String| which is a unique identifier of the container type (|List|, |Dict| and |Partial|). It is guaranteed that for the mentioned types `id(v1) ==# id(v2)` - returns true iff `type(v1) == type(v2) && v1 is v2` (note: - |v:_null_list| and |v:_null_dict| have the same `id()` with - different types because they are internally represented as - a NULL pointers). Currently `id()` returns a hexadecimal - representanion of the pointers to the containers (i.e. like - `0x994a40`), same as `printf("%p", {expr})`, but it is advised - against counting on exact format of return value. + returns true iff `type(v1) == type(v2) && v1 is v2`. + Note that |v:_null_string|, |v:_null_list|, and |v:_null_dict| + have the same `id()` with different types because they are + internally represented as a NULL pointers. `id()` returns a + hexadecimal representanion of the pointers to the containers + (i.e. like `0x994a40`), same as `printf("%p", {expr})`, + but it is advised against counting on the exact format of + return value. It is not guaranteed that `id(no_longer_existing_container)` will not be equal to some other `id()`: new containers may @@ -5607,7 +5657,6 @@ jobstart({cmd}[, {opts}]) *jobstart()* before invoking `on_stderr`. |channel-buffered| stdout_buffered: (boolean) Collect data until EOF (stream closed) before invoking `on_stdout`. |channel-buffered| - TERM: (string) Sets the `pty` $TERM environment variable. width: (number) Width of the `pty` terminal. {opts} is passed as |self| dictionary to the callback; the @@ -5816,7 +5865,7 @@ list2str({list} [, {utf8}]) *list2str()* < localtime() *localtime()* Return the current time, measured as seconds since 1st Jan - 1970. See also |strftime()| and |getftime()|. + 1970. See also |strftime()|, |strptime()| and |getftime()|. log({expr}) *log()* @@ -7860,7 +7909,7 @@ shellescape({string} [, {special}]) *shellescape()* < See also |::S|. -shiftwidth() *shiftwidth()* +shiftwidth([{col}]) *shiftwidth()* Returns the effective value of 'shiftwidth'. This is the 'shiftwidth' value unless it is zero, in which case it is the 'tabstop' value. To be backwards compatible in indent @@ -7876,7 +7925,13 @@ shiftwidth() *shiftwidth()* endif < And then use s:sw() instead of &sw. + When there is one argument {col} this is used as column number + for which to return the 'shiftwidth' value. This matters for the + 'vartabstop' feature. If no {col} argument is given, column 1 + will be assumed. + sign_define({name} [, {dict}]) *sign_define()* +sign_define({list}) Define a new sign named {name} or modify the attributes of an existing sign. This is similar to the |:sign-define| command. @@ -7886,24 +7941,38 @@ sign_define({name} [, {dict}]) *sign_define()* The {name} can be a String or a Number. The optional {dict} argument specifies the sign attributes. The following values are supported: - icon full path to the bitmap file for the sign. - linehl highlight group used for the whole line the + icon full path to the bitmap file for the sign. + linehl highlight group used for the whole line the sign is placed in. - text text that is displayed when there is no icon + text text that is displayed when there is no icon or the GUI is not being used. - texthl highlight group used for the text item - numhl highlight group used for 'number' column at the + texthl highlight group used for the text item + numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, |hl-CursorLineNr|. If the sign named {name} already exists, then the attributes of the sign are updated. - Returns 0 on success and -1 on failure. + The one argument {list} can be used to define a list of signs. + Each list item is a dictionary with the above items in {dict} + and a 'name' item for the sign name. + + Returns 0 on success and -1 on failure. When the one argument + {list} is used, then returns a List of values one for each + defined sign. Examples: > - call sign_define("mySign", {"text" : "=>", "texthl" : - \ "Error", "linehl" : "Search"}) + call sign_define("mySign", { + \ "text" : "=>", + \ "texthl" : "Error", + \ "linehl" : "Search"}) + call sign_define([ + \ {'name' : 'sign1', + \ 'text' : '=>'}, + \ {'name' : 'sign2', + \ 'text' : '!!'} + \ ]) < sign_getdefined([{name}]) *sign_getdefined()* Get a list of defined signs and their attributes. @@ -7915,14 +7984,14 @@ sign_getdefined([{name}]) *sign_getdefined()* Each list item in the returned value is a dictionary with the following entries: - icon full path to the bitmap file of the sign - linehl highlight group used for the whole line the + icon full path to the bitmap file of the sign + linehl highlight group used for the whole line the sign is placed in. - name name of the sign - text text that is displayed when there is no icon + name name of the sign + text text that is displayed when there is no icon or the GUI is not being used. - texthl highlight group used for the text item - numhl highlight group used for 'number' column at the + texthl highlight group used for the text item + numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, |hl-CursorLineNr|. @@ -8013,25 +8082,25 @@ sign_jump({id}, {group}, {expr}) < *sign_place()* sign_place({id}, {group}, {name}, {expr} [, {dict}]) - Place the sign defined as {name} at line {lnum} in file {expr} - and assign {id} and {group} to sign. This is similar to the - |:sign-place| command. + Place the sign defined as {name} at line {lnum} in file or + buffer {expr} and assign {id} and {group} to sign. This is + similar to the |:sign-place| command. If the sign identifier {id} is zero, then a new identifier is allocated. Otherwise the specified number is used. {group} is the sign group name. To use the global sign group, use an empty string. {group} functions as a namespace for {id}, thus two groups can use the same IDs. Refer to |sign-identifier| - for more information. + and |sign-group| for more information. {name} refers to a defined sign. {expr} refers to a buffer name or number. For the accepted values, see |bufname()|. The optional {dict} argument supports the following entries: - lnum line number in the buffer {expr} where - the sign is to be placed. For the - accepted values, see |line()|. + lnum line number in the file or buffer + {expr} where the sign is to be placed. + For the accepted values, see |line()|. priority priority of the sign. See |sign-priority| for more information. @@ -8060,17 +8129,85 @@ sign_place({id}, {group}, {name}, {expr} [, {dict}]) call sign_place(10, 'g3', 'sign4', 'json.c', \ {'lnum' : 40, 'priority' : 90}) < + *sign_placelist()* +sign_placelist({list}) + Place one or more signs. This is similar to the + |sign_place()| function. The {list} argument specifies the + List of signs to place. Each list item is a dict with the + following sign attributes: + buffer buffer name or number. For the accepted + values, see |bufname()|. + group sign group. {group} functions as a namespace + for {id}, thus two groups can use the same + IDs. If not specified or set to an empty + string, then the global group is used. See + |sign-group| for more information. + id sign identifier. If not specified or zero, + then a new unique identifier is allocated. + Otherwise the specified number is used. See + |sign-identifier| for more information. + lnum line number in the buffer {expr} where the + sign is to be placed. For the accepted values, + see |line()|. + name name of the sign to place. See |sign_define()| + for more information. + priority priority of the sign. When multiple signs are + placed on a line, the sign with the highest + priority is used. If not specified, the + default value of 10 is used. See + |sign-priority| for more information. + + If {id} refers to an existing sign, then the existing sign is + modified to use the specified {name} and/or {priority}. + + Returns a List of sign identifiers. If failed to place a + sign, the corresponding list item is set to -1. + + Examples: > + " Place sign s1 with id 5 at line 20 and id 10 at line + " 30 in buffer a.c + let [n1, n2] = sign_place([ + \ {'id' : 5, + \ 'name' : 's1', + \ 'buffer' : 'a.c', + \ 'lnum' : 20}, + \ {'id' : 10, + \ 'name' : 's1', + \ 'buffer' : 'a.c', + \ 'lnum' : 30} + \ ]) + + " Place sign s1 in buffer a.c at line 40 and 50 + " with auto-generated identifiers + let [n1, n2] = sign_place([ + \ {'name' : 's1', + \ 'buffer' : 'a.c', + \ 'lnum' : 40}, + \ {'name' : 's1', + \ 'buffer' : 'a.c', + \ 'lnum' : 50} + \ ]) +< sign_undefine([{name}]) *sign_undefine()* +sign_undefine({list}) Deletes a previously defined sign {name}. This is similar to the |:sign-undefine| command. If {name} is not supplied, then deletes all the defined signs. - Returns 0 on success and -1 on failure. + The one argument {list} can be used to undefine a list of + signs. Each list item is the name of a sign. + + Returns 0 on success and -1 on failure. For the one argument + {list} call, returns a list of values one for each undefined + sign. Examples: > " Delete a sign named mySign call sign_undefine("mySign") + " Delete signs 'sign1' and 'sign2' + call sign_undefine(["sign1", "sign2"]) + " Delete all the signs call sign_undefine() < @@ -8116,6 +8253,32 @@ sign_unplace({group} [, {dict}]) *sign_unplace()* " Remove all the placed signs from all the buffers call sign_unplace('*') < +sign_unplacelist({list}) *sign_unplacelist()* + Remove previously placed signs from one or more buffers. This + is similar to the |sign_unplace()| function. + + The {list} argument specifies the List of signs to remove. + Each list item is a dict with the following sign attributes: + buffer buffer name or number. For the accepted + values, see |bufname()|. If not specified, + then the specified sign is removed from all + the buffers. + group sign group name. If not specified or set to an + empty string, then the global sign group is + used. If set to '*', then all the groups + including the global group are used. + id sign identifier. If not specified, then all + the signs in the specified group are removed. + + Returns a List where an entry is set to 0 if the corresponding + sign was successfully removed or -1 on failure. + + Example: > + " Remove sign with id 10 from buffer a.vim and sign + " with id 20 from buffer b.vim + call sign_unplace([{'id' : 10, 'buffer' : "a.vim"}, + \ {'id' : 20, 'buffer' : 'b.vim'}]) +< simplify({filename}) *simplify()* Simplify the file name as much as possible without changing the meaning. Shortcuts (on MS-Windows) or symbolic links (on @@ -8451,7 +8614,7 @@ strftime({format} [, {time}]) *strftime()* {format} depends on your system, thus this is not portable! See the manual page of the C function strftime() for the format. The maximum length of the result is 80 characters. - See also |localtime()| and |getftime()|. + See also |localtime()|, |getftime()| and |strptime()|. The language can be changed with the |:language| command. Examples: > :echo strftime("%c") Sun Apr 27 11:49:23 1997 @@ -8541,6 +8704,31 @@ strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* example, to get the character under the cursor: > strpart(getline("."), col(".") - 1, 1, v:true) < +strptime({format}, {timestring}) *strptime()* + The result is a Number, which is a unix timestamp representing + the date and time in {timestring}, which is expected to match + the format specified in {format}. + + The accepted {format} depends on your system, thus this is not + portable! See the manual page of the C function strptime() + for the format. Especially avoid "%c". The value of $TZ also + matters. + + If the {timestring} cannot be parsed with {format} zero is + returned. If you do not know the format of {timestring} you + can try different {format} values until you get a non-zero + result. + + See also |strftime()|. + Examples: > + :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23") +< 862156163 > + :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55")) +< Sun Apr 27 11:53:55 1997 > + :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600) +< Sun Apr 27 12:53:55 1997 + + strridx({haystack}, {needle} [, {start}]) *strridx()* The result is a Number, which gives the byte index in {haystack} of the last occurrence of the String {needle}. @@ -9335,6 +9523,25 @@ win_screenpos({nr}) *win_screenpos()* Return [0, 0] if the window cannot be found in the current tabpage. +win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* + Move the window {nr} to a new split of the window {target}. + This is similar to moving to {target}, creating a new window + using |:split| but having the same contents as window {nr}, and + then closing {nr}. + + Both {nr} and {target} can be window numbers or |window-ID|s. + + Returns zero for success, non-zero for failure. + + {options} is a Dictionary with the following optional entries: + "vertical" When TRUE, the split is created vertically, + like with |:vsplit|. + "rightbelow" When TRUE, the split is made below or to the + right (if vertical). When FALSE, it is done + above or to the left (if vertical). When not + present, the values of 'splitbelow' and + 'splitright' are used. + *winbufnr()* winbufnr({nr}) The result is a Number, which is the number of the buffer associated with window {nr}. {nr} can be the window number or @@ -9771,15 +9978,49 @@ change their contents. Thus you can pass a |List| to a function and have the function add an item to it. If you want to make sure the function cannot change a |List| or |Dictionary| use |:lockvar|. -When not using "...", the number of arguments in a function call must be equal -to the number of named arguments. When using "...", the number of arguments -may be larger. - It is also possible to define a function without any arguments. You must still supply the () then. It is allowed to define another function inside a function body. + *optional-function-argument* +You can provide default values for positional named arguments. This makes +them optional for function calls. When a positional argument is not +specified at a call, the default expression is used to initialize it. +This only works for functions declared with |function|, not for lambda +expressions |expr-lambda|. + +Example: > + function Something(key, value = 10) + echo a:key .. ": " .. a:value + endfunction + call Something('empty') "empty: 10" + call Something('key', 20) "key: 20" + +The argument default expressions are evaluated at the time of the function +call, not definition. Thus it is possible to use an expression which is +invalid the moment the function is defined. The expressions are also only +evaluated when arguments are not specified during a call. + + *E989* +Optional arguments with default expressions must occur after any mandatory +arguments. You can use "..." after all optional named arguments. + +It is possible for later argument defaults to refer to prior arguments, +but not the other way around. They must be prefixed with "a:", as with all +arguments. + +Example that works: > + :function Okay(mandatory, optional = a:mandatory) + :endfunction +Example that does NOT work: > + :function NoGood(first = a:second, second = 10) + :endfunction +< +When not using "...", the number of arguments in a function call must be equal +to the number of mandatory named arguments. When using "...", the number of +arguments may be larger. + *local-variables* Inside a function local variables can be used. These will disappear when the function returns. Global variables need to be accessed with "g:". @@ -9857,6 +10098,8 @@ This function can then be called with: > The recursiveness of user functions is restricted with the |'maxfuncdepth'| option. +It is also possible to use `:eval`. It does not support a range. + AUTOMATICALLY LOADING FUNCTIONS ~ *autoload-functions* @@ -10290,6 +10533,20 @@ text... Unlock the internal variable {name}. Does the opposite of |:lockvar|. + *:eval* +:eval {expr} Evaluate {expr} and discard the result. Example: > + :eval append(Filter(Getlist()), '$') + +< The expression is supposed to have a side effect, + since the resulting value is not used. In the example + the `append()` call appends the List with text to the + buffer. This is similar to `:call` but works with any + expression. + + The command can be shortened to `:ev` or `:eva`, but + these are hard to recognize and therefore not to be + used. + :if {expr1} *:if* *:end* *:endif* *:en* *E171* *E579* *E580* :en[dif] Execute the commands until the next matching ":else" diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 172821ac28..c824a9f9f6 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1144,6 +1144,7 @@ tag command action ~ |:bNext| :bN[ext] go to previous buffer in the buffer list |:ball| :ba[ll] open a window for each buffer in the buffer list |:badd| :bad[d] add buffer to the buffer list +|:balt| :balt like ":badd" but also set the alternate file |:bdelete| :bd[elete] remove a buffer from the buffer list |:behave| :be[have] set mouse and selection behavior |:belowright| :bel[owright] make split window appear right or below diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index c4b93a2a27..6902ed5fd4 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -287,8 +287,7 @@ character is written at the end of each line. Thus if you want to insert a *i_CTRL-X* *insert_expand* CTRL-X enters a sub-mode where several commands can be used. Most of these -commands do keyword completion; see |ins-completion|. These are not available -when Vim was compiled without the |+insert_expand| feature. +commands do keyword completion; see |ins-completion|. Two commands can be used to scroll the window up or down, without exiting insert mode: @@ -592,9 +591,6 @@ In Insert and Replace mode, there are several commands to complete part of a keyword or line that has been typed. This is useful if you are using complicated keywords (e.g., function names with capitals and underscores). -These commands are not available when the |+insert_expand| feature was -disabled at compile time. - Completion can be done for: 1. Whole lines |i_CTRL-X_CTRL-L| diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 06666c3a27..3c0dbf96c5 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -397,6 +397,11 @@ LSP HIGHLIGHT *lsp-highlight* Reference Highlights: +Highlight groups that are meant to be used by |vim.lsp.buf.document_highlight()|. + +You can see more about the differences in types here: +https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight + *hl-LspReferenceText* LspReferenceText used for highlighting "text" references *hl-LspReferenceRead* @@ -744,15 +749,6 @@ start_client({config}) *vim.lsp.start_client()* The following parameters describe fields in the {config} table. -> - - -- In attach function for the client, you can do: - local custom_attach = function(client) - if client.config.flags then - client.config.flags.allow_incremental_sync = true - end - end -< Parameters: ~ {root_dir} (required, string) Directory where the @@ -794,6 +790,8 @@ start_client({config}) *vim.lsp.start_client()* See `initialize` in the LSP spec. {name} (string, default=client-id) Name in log messages. + {get_language_id} function(bufnr, filetype) -> language + ID as string. Defaults to the filetype. {offset_encoding} (default="utf-16") One of "utf-8", "utf-16", or "utf-32" which is the encoding that the LSP server expects. @@ -849,8 +847,8 @@ start_client({config}) *vim.lsp.start_client()* {flags} A table with flags for the client. The current (experimental) flags are: • allow_incremental_sync (bool, default - false): Allow using on_line callbacks - for lsp + true): Allow using incremental sync + for buffer edits Return: ~ Client id. |vim.lsp.get_client_by_id()| Note: client may @@ -932,15 +930,21 @@ definition() *vim.lsp.buf.definition()* Jumps to the definition of the symbol under the cursor. document_highlight() *vim.lsp.buf.document_highlight()* - Send request to server to resolve document highlights for the - current text document position. This request can be associated - to key mapping or to events such as `CursorHold` , eg: + Send request to the server to resolve document highlights for + the current text document position. This request can be + triggered by a key mapping or by events such as `CursorHold` , + eg: > vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] < + Note: Usage of |vim.lsp.buf.document_highlight()| requires the + following highlight groups to be defined or you won't be able + to see the actual highlights. |LspReferenceText| + |LspReferenceRead| |LspReferenceWrite| + document_symbol() *vim.lsp.buf.document_symbol()* Lists all symbols in the current buffer in the quickfix window. @@ -1307,6 +1311,17 @@ on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config}) • Update diagnostics in InsertMode or wait until InsertLeave +reset({client_id}, {buffer_client_map}) *vim.lsp.diagnostic.reset()* + Clear diagnotics and diagnostic cache + + Handles saving diagnostics from multiple clients in the same + buffer. + + Parameters: ~ + {client_id} number + {buffer_client_map} table map of buffers to active + clients + save({diagnostics}, {bufnr}, {client_id}) *vim.lsp.diagnostic.save()* Save diagnostics to the current buffer. @@ -1447,11 +1462,30 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) ============================================================================== Lua module: vim.lsp.handlers *lsp-handlers* - *vim.lsp.handlers.progress_callback()* -progress_callback({_}, {_}, {params}, {client_id}) + *vim.lsp.handlers.progress_handler()* +progress_handler({_}, {_}, {params}, {client_id}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand + *vim.lsp.handlers.signature_help()* +signature_help({_}, {method}, {result}, {_}, {bufnr}, {config}) + Parameters: ~ + {config} table Configuration table. + • border: (default=nil) + • Add borders to the floating window + • See |vim.api.nvim_open_win()| + + See also: ~ + https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation|lsp-handler| for the method "textDocument/signatureHelp"> + + vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( + vim.lsp.handlers.signature_help, { + -- Use a sharp border with `FloatBorder` highlights + border = "single" + } + ) +< + ============================================================================== Lua module: vim.lsp.util *lsp-util* @@ -1525,6 +1559,25 @@ close_preview_autocmd({events}, {winnr}) See also: ~ |autocmd-events| + *vim.lsp.util.compute_diff()* +compute_diff({old_lines}, {new_lines}, {start_line_idx}, {end_line_idx}, + {offset_encoding}) + Returns the range table for the difference between old and new + lines + + Parameters: ~ + {old_lines} table list of lines + {new_lines} table list of lines + {start_line_idx} int line to begin search for first + difference + {end_line_idx} int line to begin search for last + difference + {offset_encoding} string encoding requested by language + server + + Return: ~ + table start_line_idx and start_col_idx of range + *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) Converts any of `MarkedString` | `MarkedString[]` | @@ -1559,6 +1612,12 @@ convert_signature_help_to_markdown_lines({signature_help}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp +create_file({change}) *vim.lsp.util.create_file()* + TODO: Documentation + +delete_file({change}) *vim.lsp.util.delete_file()* + TODO: Documentation + *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. @@ -1766,12 +1825,12 @@ make_workspace_params({added}, {removed}) {removed} *vim.lsp.util.open_floating_preview()* -open_floating_preview({contents}, {filetype}, {opts}) +open_floating_preview({contents}, {syntax}, {opts}) Shows contents in a floating window. Parameters: ~ {contents} table of lines to show in window - {filetype} string of filetype to set for opened buffer + {syntax} string of syntax to set for opened buffer {opts} dictionary with optional fields Return: ~ @@ -1802,6 +1861,10 @@ preview_location({location}) *vim.lsp.util.preview_location()* (bufnr,winnr) buffer and window number of floating window or nil +rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* + Parameters: ~ + {opts} (table) + set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()* Replaces text in a range with new text. diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0bbed56662..6d007c0e44 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -18,7 +18,8 @@ an idea of what lurks beneath: > Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the "editor stdlib" (|functions| and Ex commands) and the |API|, all of which can -be used from Lua code. +be used from Lua code. A good overview of using Lua in neovim is given by +https://github.com/nanotee/nvim-lua-guide. Module conflicts are resolved by "last wins". For example if both of these are on 'runtimepath': @@ -831,6 +832,7 @@ LUA-VIMSCRIPT BRIDGE *lua-vimscript* Nvim Lua provides an interface to Vimscript variables and functions, and editor commands and options. +See also https://github.com/nanotee/nvim-lua-guide. vim.call({func}, {...}) *vim.call()* Invokes |vim-function| or |user-function| {func} with arguments {...}. @@ -1055,6 +1057,18 @@ list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* See also: ~ |vim.tbl_extend()| +list_slice({list}, {start}, {finish}) *vim.list_slice()* + Creates a copy of a table containing only elements from start + to end (inclusive) + + Parameters: ~ + {list} table table + {start} integer Start range of slice + {finish} integer End range of slice + + Return: ~ + Copy of table sliced from start to finish (inclusive) + pesc({s}) *vim.pesc()* Escapes magic chars in a Lua pattern. diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index edec4a8de7..73ac3763d0 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -224,6 +224,20 @@ text before the cursor and start omni completion when some condition is met. For abbreviations |v:char| is set to the character that was typed to trigger the abbreviation. You can use this to decide how to expand the {lhs}. You should not either insert or change the v:char. + +Also, keep in mind that the expression may be evaluated when looking for +typeahead, before the previous command has been executed. For example: > + func StoreColumn() + let g:column = col('.') + return 'x' + endfunc + nnoremap <expr> x StoreColumn() + nmap ! f!x +You will notice that g:column has the value from before executing "fx", +because "z" is evaluated before "fx" is executed. +This can be solved by inserting <Ignore> before the character that is +expression-mapped: > + nmap ! f!<Ignore>x Be very careful about side effects! The expression is evaluated while obtaining characters, you may very well make the command dysfunctional. @@ -276,7 +290,7 @@ as a special key. *<Cmd>* *:map-cmd* The <Cmd> pseudokey begins a "command mapping", which executes the command directly (without changing modes). Where you might use ":...<CR>" in the -{lhs} of a mapping, you can instead use "<Cmd>...<CR>". +{rhs} of a mapping, you can instead use "<Cmd>...<CR>". Example: > noremap x <Cmd>echo mode(1)<cr> < @@ -300,7 +314,7 @@ Note: *E5520* <Cmd> commands must terminate, that is, they must be followed by <CR> in the -{lhs} of the mapping definition. |Command-line| mode is never entered. +{rhs} of the mapping definition. |Command-line| mode is never entered. 1.3 MAPPING AND MODES *:map-modes* diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index bec2b362ea..5885b20ab7 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -312,6 +312,8 @@ Other commands ~ *:Program* jump to the window with the running program *:Source* jump to the window with the source code, create it if there isn't one + *:Asm* jump to the window with the disassembly, create it if there + isn't one Prompt mode ~ @@ -330,6 +332,12 @@ This works slightly differently: Prompt mode can be used even when the |+terminal| feature is present with: > let g:termdebug_use_prompt = 1 +< + *termdebug_disasm_window* +If you want the Asm window shown by default, set this to 1. Setting to +any value greater than 1 will set the Asm window height to that value: > + let g:termdebug_disasm_window = 15 +< Communication ~ *termdebug-communication* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index a497efa47e..63a9db9d0d 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1971,6 +1971,8 @@ A jump table for the options with a short description can be found at |Q_op|. foldcolumn:{n} Set the 'foldcolumn' option to {n} when starting diff mode. Without this 2 is used. + followwrap Follow the 'wrap' option and leave as it is. + internal Use the internal diff library. This is ignored when 'diffexpr' is set. *E960* When running out of memory when writing a @@ -3714,6 +3716,10 @@ A jump table for the options with a short description can be found at |Q_op|. *lcs-space* space:c Character to show for a space. When omitted, spaces are left blank. + *lcs-lead* + lead:c Character to show for leading spaces. When omitted, + leading spaces are blank. Overrides the "space" + setting for leading spaces. *lcs-trail* trail:c Character to show for trailing spaces. When omitted, trailing spaces are blank. Overrides the "space" @@ -4088,8 +4094,6 @@ A jump table for the options with a short description can be found at |Q_op|. In the "popup" model the right mouse button produces a pop-up menu. You need to define this first, see |popup-menu|. - In a terminal the popup menu works if Vim is compiled with the - |+insert_expand| option. Note that you can further refine the meaning of buttons with mappings. See |mouse-overview|. But mappings are NOT used for modeless selection. @@ -4814,7 +4818,7 @@ A jump table for the options with a short description can be found at |Q_op|. |xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains preferences from system administrator. 3. Data home directory, for plugins installed by user. - Given by `stdpath("data")`. |$XDG_DATA_HOME| + Given by `stdpath("data")/site`. |$XDG_DATA_HOME| 4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS. This is for plugins which were installed by system administrator, but are not part of the Nvim distribution. XDG_DATA_DIRS defaults @@ -4856,7 +4860,9 @@ A jump table for the options with a short description can be found at |Q_op|. local to window Number of lines to scroll with CTRL-U and CTRL-D commands. Will be set to half the number of lines in the window when the window size - changes. If you give a count to the CTRL-U or CTRL-D command it will + changes. This may happen when enabling the |status-line| or + 'tabline' option after setting the 'scroll' option. + If you give a count to the CTRL-U or CTRL-D command it will be used as the new value for 'scroll'. Reset to half the window height with ":set scroll=0". @@ -5549,6 +5555,12 @@ A jump table for the options with a short description can be found at |Q_op|. "number" display signs in the 'number' column. If the number column is not present, then behaves like 'auto'. + Note regarding 'orphaned signs': with signcolumn numbers higher than + 1, deleting lines will also remove the associated signs automatically, + in contrast to the default Vim behavior of keeping and grouping them. + This is done in order for the signcolumn appearence not appear weird + during line deletion. + *'smartcase'* *'scs'* *'nosmartcase'* *'noscs'* 'smartcase' 'scs' boolean (default off) @@ -5619,6 +5631,9 @@ A jump table for the options with a short description can be found at |Q_op|. The 'L' flag in 'cpoptions' changes how tabs are used when 'list' is set. + The value of 'softtabstop' will be ignored if |'varsofttabstop'| is set + to anything other than an empty string. + *'spell'* *'nospell'* 'spell' boolean (default off) local to window @@ -6156,6 +6171,9 @@ A jump table for the options with a short description can be found at |Q_op|. though. Otherwise aligned comments will be wrong when 'tabstop' is changed. + The value of 'tabstop' will be ignored if |'vartabstop'| is set to + anything other than an empty string. + *'tagbsearch'* *'tbs'* *'notagbsearch'* *'notbs'* 'tagbsearch' 'tbs' boolean (default on) global @@ -6289,6 +6307,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 @@ -6517,6 +6558,38 @@ A jump table for the options with a short description can be found at |Q_op|. written to disk (see |crash-recovery|). Also used for the |CursorHold| autocommand event. + *'varsofttabstop'* *'vsts'* +'varsofttabstop' 'vsts' string (default "") + local to buffer + A list of the number of spaces that a <Tab> counts for while editing, + such as inserting a <Tab> or using <BS>. It "feels" like variable- + width <Tab>s are being inserted, while in fact a mixture of spaces + and <Tab>s is used. Tab widths are separated with commas, with the + final value applying to all subsequent tabs. + + For example, when editing assembly language files where statements + start in the 8th column and comments in the 40th, it may be useful + to use the following: > + :set varsofttabstop=8,32,8 +< This will set soft tabstops at the 8th and 40th columns, and at every + 8th column thereafter. + + Note that the value of |'softtabstop'| will be ignored while + 'varsofttabstop' is set. + + *'vartabstop'* *'vts'* +'vartabstop' 'vts' string (default "") + local to buffer + A list of the number of spaces that a <Tab> in the file counts for, + separated by commas. Each value corresponds to one tab, with the + final value applying to all subsequent tabs. For example: > + :set vartabstop=4,20,10,8 +< This will make the first tab 4 spaces wide, the second 20 spaces, + the third 10 spaces, and all following tabs 8 spaces. + + Note that the value of |'tabstop'| will be ignored while 'vartabstop' + is set. + *'verbose'* *'vbs'* 'verbose' 'vbs' number (default 0) global diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 9a75a95f23..7312ab721b 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -1,9 +1,9 @@ -*pi_netrw.txt* For Vim version 8.1. Last change: 2019 Jul 17 +*pi_netrw.txt* For Vim version 8.2. Last change: 2020 Aug 15 ------------------------------------------------ NETRW REFERENCE MANUAL by Charles E. Campbell ------------------------------------------------ -Author: Charles E. Campbell <NdrOchip@ScampbellPfamily.AbizM> +Author: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> (remove NOSPAM from Campbell's email first) Copyright: Copyright (C) 2017 Charles E Campbell *netrw-copyright* @@ -1565,8 +1565,8 @@ So, for example: > file.rcs,v -> NFH_rcsCOMMAv() < If more such translations are necessary, please send me email: > - NdrOchip at ScampbellPfamily.AbizM - NOSPAM -with a request. + NcampObell@SdrPchip.AorgM-NOSPAM +with a request. (remove the embedded NOSPAM first) Associated setting variable: |g:netrw_browsex_viewer| @@ -2744,9 +2744,8 @@ your browsing preferences. (see also: |netrw-settings|) *g:netrw_home* The home directory for where bookmarks and history are saved (as .netrwbook and .netrwhist). - Netrw uses |expand()|on the string. - default: the first directory on the - |'runtimepath'| + Netrw uses |expand()| on the string. + default: stdpath('data') (see |stdpath()|) *g:netrw_keepdir* =1 (default) keep current directory immune from the browsing directory. @@ -3054,7 +3053,7 @@ your browsing preferences. (see also: |netrw-settings|) (see |netrw-c-tab|). *g:netrw_xstrlen* Controls how netrw computes string lengths, - including multi-byte characters' string + including multibyte characters' string length. (thanks to N Weibull, T Mechelynck) =0: uses Vim's built-in strlen() =1: number of codepoints (Latin a + combining @@ -3229,7 +3228,8 @@ If there are marked files: (see |netrw-mf|) R [query: reply with s/^\(.*\)\.c$/\1.cpp/] < This example will mark all *.c files and then rename them to *.cpp - files. + files. Netrw will protect you from overwriting local files without + confirmation, but not remote ones. The ctrl-X character has special meaning for renaming files: > @@ -3512,7 +3512,7 @@ Example: Clear netrw's marked file list via a mapping on gu > - Click "Add..." - Set External Editor (adjust path as needed, include the quotes and !.! at the end): - "c:\Program Files\Vim\vim70\gvim.exe" !.! + "c:\Program Files\Vim\vim82\gvim.exe" !.! - Check that the filetype in the box below is {asterisk}.{asterisk} (all files), or whatever types you want (cec: change {asterisk} to * ; I had to @@ -3762,8 +3762,8 @@ by obtaining a copy of the latest (often developmental) netrw at: The <netrw.vim> script is typically installed on systems as something like: > - /usr/local/share/vim/vim7x/plugin/netrwPlugin.vim - /usr/local/share/vim/vim7x/autoload/netrw.vim + /usr/local/share/vim/vim8x/plugin/netrwPlugin.vim + /usr/local/share/vim/vim8x/autoload/netrw.vim (see output of :echo &rtp) < which is loaded automatically at startup (assuming :set nocp). If you @@ -3836,12 +3836,30 @@ netrw: Please send that information to <netrw.vim>'s maintainer along with the o/s you're using and the vim version that you're using - (see |:version|) > - NdrOchip at ScampbellPfamily.AbizM - NOSPAM + (see |:version|) (remove the embedded NOSPAM first) > + NcampObell@SdrPchip.AorgM-NOSPAM < ============================================================================== 12. History *netrw-history* {{{1 + v169: Dec 20, 2019 * (reported by amkarthik) that netrw's x + (|netrw-x|) would throw an error when + attempting to open a local directory. + v168: Dec 12, 2019 * scp timeout error message not reported, + hopefully now fixed (Shane Xb Qian) + + v167: Nov 29, 2019 * netrw does a save&restore on @* and @+. + That causes problems with the clipboard. + Now restores occurs only if @* or @+ have + been changed. + * netrw will change @* or @+ less often. + Never if I happen to have caught all the + operations that modify the unnamed + register (which also writes @*). + * Modified hiding behavior so that "s" + will not ignore hiding. + v166: Nov 06, 2019 * Removed a space from a nmap for "-" + * Numerous debugging statement changes v163: Dec 05, 2017 * (Cristi Balan) reported that a setting ('sel') was left changed * (Holger Mitschke) reported a problem with @@ -3852,6 +3870,8 @@ netrw: * (Holger Mitschke) amended this help file with additional |g:netrw_special_syntax| items + * Prioritized wget over curl for + g:netrw_http_cmd v162: Sep 19, 2016 * (haya14busa) pointed out two syntax errors with a patch; these are now fixed. Oct 26, 2016 * I started using mate-terminal and found that @@ -4265,7 +4285,7 @@ netrw: Vim editor by Bram Moolenaar (Thanks, Bram!) dav support by C Campbell fetch support by Bram Moolenaar and C Campbell - ftp support by C Campbell <NdrOchip@ScampbellPfamily.AbizM> + ftp support by C Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> http support by Bram Moolenaar <bram@moolenaar.net> rcp rsync support by C Campbell (suggested by Erik Warendorph) diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index 59b318b7fd..c6c0596ea0 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -1,12 +1,12 @@ -*pi_tar.txt* Nvim +*pi_tar.txt* For Vim version 8.2. Last change: 2020 Jan 07 +====================+ | Tar File Interface | +====================+ -Author: Charles E. Campbell <NdrOchip@ScampbellPfamily.AbizM> +Author: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> (remove NOSPAM from Campbell's email first) -Copyright 2005-2012: *tar-copyright* +Copyright 2005-2017: *tar-copyright* The VIM LICENSE (see |copyright|) applies to the files in this package, including tarPlugin.vim, tar.vim, and pi_tar.txt. Like anything else that's except use "tar.vim" instead of "VIM". Like @@ -104,48 +104,67 @@ Copyright 2005-2012: *tar-copyright* ============================================================================== 4. History *tar-history* - - v28 Jun 23, 2011 * a few more decompression options (tbz tb2 txz) - v27 May 31, 2011 * moved cygwin detection before g:tar_copycmd handling - * inserted additional |:keepj| modifiers - * changed silent to sil! (|:silent|) - v26 Aug 09, 2010 * uses buffer-local instead of window variables to hold - tarfile name - * inserted keepj before 0d to protect jump list - v25 Jun 19, 2010 * (Jan Steffens) added support for xz compression - v24 Apr 07, 2009 * :Untarvim command implemented - Sep 28, 2009 * Added lzma support - v22 Aug 08, 2008 * security fixes - v16 Jun 06, 2008 * tarfile:: used instead of tarfile: when editing files - inside tarballs. Fixes a problem with tarballs called - things like c:\abc.tar. (tnx to Bill McCarthy) - v14 May 09, 2008 * arno caught a security bug - May 28, 2008 * various security improvements. Now requires patch 299 - which provides the fnameescape() function - May 30, 2008 * allows one to view *.gz and *.bz2 files that are in - *.tar files. - v12 Sep 07, 2007 * &shq now used if not the empty string for g:tar_shq - v10 May 02, 2006 * now using "redraw then echo" to show messages, instead - of "echo and prompt user" - v9 May 02, 2006 * improved detection of masquerading as tar file - v8 May 02, 2006 * allows editing of files that merely masquerade as tar - files - v7 Mar 22, 2006 * work on making tar plugin work across network - Mar 27, 2006 * g:tar_cmd now available for users to change the name - of the tar program to be used. By default, of course, - it's "tar". - v6 Dec 21, 2005 * writing to files not in directories caused problems - - fixed (pointed out by Christian Robinson) - v5 Nov 22, 2005 * report option workaround installed - v3 Sep 16, 2005 * handles writing files in an archive back to the - archive - Oct 18, 2005 * <amatch> used instead of <afile> in autocmds - Oct 18, 2005 * handles writing to compressed archives - Nov 03, 2005 * handles writing tarfiles across a network using - netrw#NetWrite() - v2 * converted to use Vim7's new autoload feature by - Bram Moolenaar - v1 (original) * Michael Toren (see http://michael.toren.net/code/) + v31 Apr 02, 2017 * (klartext) reported that browsing encrypted + files in a zip archive created unencrypted + swap files. I am applying a similar fix + used on zip.vim to tar.vim: new buffers + are opened with |:noswapfile|. + May 16, 2017 * When the mouse option isn't empty, the + leftmouse can be used to select a file + in the tar-file listing. + v30 Apr 22, 2014 * .tgz files are ambiguous: they may have been + compressed with either gzip or bzip2. Tar.vim + disambiguates by using unix's "file" command. + Feb 18, 2016 * Changed =~ to =~# where appropriate + Feb 18, 2017 * Now also permits xz decompression + v28 Jun 23, 2011 * a few more decompression options (tbz tb2 txz) + v27 May 31, 2011 * moved cygwin detection before g:tar_copycmd + handling + * inserted additional |:keepj| modifiers + * changed silent to sil! (|:silent|) + v26 Aug 09, 2010 * uses buffer-local instead of window variables + to hold tarfile name + * inserted keepj before 0d to protect jump list + v25 Jun 19, 2010 * (Jan Steffens) added support for xz + compression + v24 Apr 07, 2009 * :Untarvim command implemented + Sep 28, 2009 * Added lzma support + v22 Aug 08, 2008 * security fixes + v16 Jun 06, 2008 * tarfile:: used instead of tarfile: when + editing files inside tarballs. Fixes a + problem with tarballs called things like + c:\abc.tar. (tnx to Bill McCarthy) + v14 May 09, 2008 * arno caught a security bug + May 28, 2008 * various security improvements. Now requires + patch 299 which provides the fnameescape() + function + May 30, 2008 * allows one to view *.gz and *.bz2 files that + are in *.tar files. + v12 Sep 07, 2007 * &shq now used if not the empty string for + g:tar_shq + v10 May 02, 2006 * now using "redraw then echo" to show messages, + instead of "echo and prompt user" + v9 May 02, 2006 * improved detection of masquerading as tar file + v8 May 02, 2006 * allows editing of files that merely masquerade + as tar files + v7 Mar 22, 2006 * work on making tar plugin work across network + Mar 27, 2006 * g:tar_cmd now available for users to change + the name of the tar program to be used. By + default, of course, it's "tar". + v6 Dec 21, 2005 * writing to files not in directories caused + problems - fixed (pointed out by + Christian Robinson) + v5 Nov 22, 2005 * report option workaround installed + v3 Sep 16, 2005 * handles writing files in an archive back to + the archive + Oct 18, 2005 * <amatch> used instead of <afile> in autocmds + Oct 18, 2005 * handles writing to compressed archives + Nov 03, 2005 * handles writing tarfiles across a network + using netrw#NetWrite() + v2 * converted to use Vim7's new autoload feature + by Bram Moolenaar + v1 (original) * Michael Toren + (see http://michael.toren.net/code/) ============================================================================== vim:tw=78:ts=8:noet:ft=help diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index f944689d0b..be895f9e4e 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -88,7 +88,7 @@ Example using pyenv: > pyenv install 3.4.4 pyenv virtualenv 3.4.4 py3nvim pyenv activate py3nvim - pip install pynvim + python3 -m pip install pynvim pyenv which python # Note the path The last command reports the interpreter path, add it to your init.vim: > let g:python3_host_prog = '/path/to/py3nvim/bin/python' diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 604c969c64..3a7337d2e8 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -804,6 +804,19 @@ DEFINING BREAKPOINTS < Note that this only works for commands that are executed when sourcing the file, not for a function defined in that file. +:breaka[dd] expr {expression} + Sets a breakpoint, that will break whenever the {expression} + evaluates to a different value. Example: > + :breakadd expr g:lnum + +< Will break, whenever the global variable lnum changes. + Note if you watch a |script-variable| this will break + when switching scripts, since the script variable is only + valid in the script where it has been defined and if that + script is called from several other scripts, this will stop + whenever that particular variable will become visible or + unaccessible again. + The [lnum] is the line number of the breakpoint. Vim will stop at or after this line. When omitted line 1 is used. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 4a99aa47bf..d3647246fa 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -106,8 +106,6 @@ argument. This can be used to find out where time is spent while loading your |config|, plugins and opening the first file. When {fname} already exists new messages are appended. - (Only available when compiled with the |+startuptime| - feature). *-+* +[num] The cursor will be positioned on line "num" for the first diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 7da886dabd..aeee02a1e0 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1715,7 +1715,7 @@ There are several html preprocessor languages out there. html.vim has been written such that it should be trivial to include it. To do so add the following two lines to the syntax coloring file for that language (the example comes from the asp.vim file): - +> runtime! syntax/html.vim syn cluster htmlPreproc add=asp @@ -4689,6 +4689,7 @@ in their own color. highlighting for groups added by the user! Uses the current value of 'background' to decide which default colors to use. + If there was a default link, restore it. |:hi-link| :hi[ghlight] clear {group-name} :hi[ghlight] {group-name} NONE diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 23db809543..7d09ca86ac 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -337,11 +337,11 @@ the same as above, with a "p" prepended. A static tag is a tag that is defined for a specific file. In a C program this could be a static function. -In Vi jumping to a tag sets the current search pattern. This means that -the "n" command after jumping to a tag does not search for the same pattern -that it did before jumping to the tag. Vim does not do this as we consider it -to be a bug. You can still find the tag search pattern in the search history. -If you really want the old Vi behavior, set the 't' flag in 'cpoptions'. +In Vi jumping to a tag sets the current search pattern. This means that the +"n" command after jumping to a tag does not search for the same pattern that +it did before jumping to the tag. Vim does not do this as we consider it to +be a bug. If you really want the old Vi behavior, set the 't' flag in +'cpoptions'. *tag-binary-search* Vim uses binary searching in the tags file to find the desired tag quickly @@ -419,8 +419,7 @@ would otherwise go unnoticed. Example: > In Vi the ":tag" command sets the last search pattern when the tag is searched for. In Vim this is not done, the previous search pattern is still remembered, -unless the 't' flag is present in 'cpoptions'. The search pattern is always -put in the search history, so you can modify it if searching fails. +unless the 't' flag is present in 'cpoptions'. *tags-option* The 'tags' option is a list of file names. Each of these files is searched @@ -847,19 +846,25 @@ like |CTRL-]|. The function used for generating the taglist is specified by setting the 'tagfunc' option. The function will be called with three arguments: - a:pattern The tag identifier used during the tag search. - a:flags List of flags to control the function behavior. + a:pattern The tag identifier or pattern used during the tag search. + a:flags String containing flags to control the function behavior. a:info Dict containing the following entries: buf_ffname Full filename which can be used for priority. user_data Custom data String, if stored in the tag stack previously by tagfunc. -Currently two flags may be passed to the tag function: +Currently up to three flags may be passed to the tag function: 'c' The function was invoked by a normal command being processed (mnemonic: the tag function may use the context around the cursor to perform a better job of generating the tag list.) 'i' In Insert mode, the user was completing a tag (with - |i_CTRL-X_CTRL-]|). + |i_CTRL-X_CTRL-]| or 'completeopt' contains `t`). + 'r' The first argument to tagfunc should be interpreted as a + |pattern| (see |tag-regexp|), such as when using: > + :tag /pat +< It is also given when completing in insert mode. + If this flag is not present, the argument is usually taken + literally as the full tag name. Note that when 'tagfunc' is set, the priority of the tags described in |tag-priority| does not apply. Instead, the priority is exactly as the diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 1696d3b9ba..510585d0dd 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -32,6 +32,12 @@ retained for the lifetime of a buffer but this is subject to change. A plugin should keep a reference to the parser object as long as it wants incremental updates. + *vim.treesitter.language_version* +To check which language version is compiled with neovim, the number is stored +within `vim.treesitter.language_version`. This number is not too helpful +unless you are wondering about compatibility between different versions of +compiled grammars. + Parser files *treesitter-parsers* Parsers are the heart of tree-sitter. They are libraries that tree-sitter will @@ -359,4 +365,91 @@ identical identifiers, highlighting both as |hl-WarningMsg|: > ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (eq? @WarningMsg.left @WarningMsg.right)) +Treesitter language injection (WIP) *lua-treesitter-language-injection* + +NOTE: This is a partially implemented feature, and not usable as a default +solution yet. What is documented here is a temporary interface intended +for those who want to experiment with this feature and contribute to +its development. + +Languages can have nested languages within them, for example javascript inside +HTML. We can "inject" a treesitter parser for a child language by configuring +injection queries. Here is an example of Javascript and CSS injected into +HTML. > + + local query = [[ + (script_element (raw_text) @javascript) + (style_element (raw_text) @css) + ]]; + + local parser = vim.treesitter.get_parser(nil, nil, { + injections = {html = query} + }) + + parser:parse() + +Any capture will be treated as the node treesitter will use for the injected +language. The capture name will be used as the language. There are a couple +reserved captures that do not have this behavior + +`@language` +This will use a nodes text content as the language to be injected. + +`@content` +This will use the captured nodes content as the injected content. + +`@combined` +This will combine all matches of a pattern as one single block of content. +By default, each match of a pattern is treated as it's own block of content +and parsed independent of each other. + +`@<language>` +Any other capture name will be treated as both the language and the content. + +`@_<name>` +Any capture with a leading "_" will not be treated as a language and will have +no special processing and is useful for capturing nodes for directives. + +Injections can be configured using `directives` instead of using capture +names. Here is an example of a directive that resolves the language based on a +buffer variable instead of statically in the query. > + + local query = require("vim.treesitter.query") + + query.add_directive("inject-preprocessor!", function(_, bufnr, _, _, data) + local success, lang = pcall(vim.api.nvim_buf_get_var, bufnr, "css_preprocessor") + + data.language = success and lang or "css" + end) + +Here is the same HTML query using this directive. > + + local query = [[ + (script_element (raw_text) @javascript) + (style_element + ((raw_text) @content + (#inject-preprocessor!))) + ]]; + + local parser = vim.treesitter.get_parser(nil, nil, { + injections = {html = query} + }) + + parser:parse() + +The following properties can be attached to the metadata object provided to +the directive. + +`language` +Same as the language capture. + +`content` +A list of ranges or nodes to inject as content. These ranges and/or nodes will +be treated as combined source and will be parsed within the same context. This +differs from the `@content` capture which only captures a single node as +content. This can also be a single number that references a captured node. + +`combined` +Same as the combined capture. + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index e92e464c6a..21f5dcc815 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -613,6 +613,7 @@ String manipulation: *string-functions* iconv() convert text from one encoding to another byteidx() byte index of a character in a string byteidxcomp() like byteidx() but count composing characters + charidx() character index of a byte in a string repeat() repeat a string multiple times eval() evaluate a string expression execute() execute an Ex command and get the output @@ -787,6 +788,7 @@ Date and Time: *date-functions* *time-functions* getftime() get last modification time of a file localtime() get current time in seconds strftime() convert time to a string + strptime() convert a date/time string to time reltime() get the current or elapsed time accurately reltimestr() convert reltime() result to a string reltimefloat() convert reltime() result to a Float @@ -941,8 +943,10 @@ Signs: *sign-functions* sign_getplaced() get a list of placed signs sign_jump() jump to a sign sign_place() place a sign + sign_placelist() place a list of signs sign_undefine() undefine a sign sign_unplace() unplace a sign + sign_unplacelist() unplace a list of signs Testing: *test-functions* @@ -956,6 +960,7 @@ Testing: *test-functions* assert_true() assert that an expression is true assert_exception() assert that a command throws an exception assert_beeps() assert that a command beeps + assert_nobeep() assert that a command does not cause a beep assert_fails() assert that a command fails Timers: *timer-functions* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 808af5f544..eadc1c04a0 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -70,6 +70,8 @@ the differences. - |matchit| plugin is enabled. To disable it in your config: > :let loaded_matchit = 1 +- |g:vimsyn_embed| defaults to "l" to enable Lua highlighting + ============================================================================== 3. New Features *nvim-features* @@ -144,7 +146,6 @@ Command-line highlighting: removed in the future). Commands: - |:autocmd| accepts the `++once` flag |:checkhealth| |:drop| is always available |:Man| is available by default, with many improvements such as completion @@ -157,8 +158,6 @@ Events: |TermOpen| |UIEnter| |UILeave| - |VimResume| - |VimSuspend| |WinClosed| Functions: @@ -195,11 +194,10 @@ Options: 'cpoptions' flags: |cpo-_| 'display' flags: "msgsep" minimizes scrolling when showing messages 'guicursor' works in the terminal - 'fillchars' flags: "msgsep" (see 'display'), "eob" for |hl-EndOfBuffer| - marker, "foldopen", "foldsep", "foldclose" + 'fillchars' flags: "msgsep" (see 'display'), "foldopen", "foldsep", + "foldclose" 'foldcolumn' supports up to 9 dynamic/fixed columns 'inccommand' shows interactive results for |:substitute|-like commands - 'listchars' local to window 'pumblend' pseudo-transparent popupmenu 'scrollback' 'signcolumn' supports up to 9 dynamic/fixed columns diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 2c3ffcbe9a..35efb1bbce 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -1039,6 +1039,11 @@ list of buffers. |unlisted-buffer| line when the buffer is first entered. Note that other commands after the + will be ignored. + *:balt* +:balt [+lnum] {fname} + Like `:badd` and also set the alternate file for the current + window to {fname}. + :[N]bd[elete][!] *:bd* *:bdel* *:bdelete* *E516* :bd[elete][!] [N] Unload buffer [N] (default: current buffer) and delete it from diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 53fd66c4df..724a96cb01 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2020 Apr 29 +" Last Change: 2021 Apr 05 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -164,6 +164,9 @@ au BufNewFile,BufRead *.mar setf vmasm " Atlas au BufNewFile,BufRead *.atl,*.as setf atlas +" Atom is based on XML +au BufNewFile,BufRead *.atom setf xml + " Autoit v3 au BufNewFile,BufRead *.au3 setf autoit @@ -212,6 +215,9 @@ au BufNewFile,BufRead *.bc setf bc " BDF font au BufNewFile,BufRead *.bdf setf bdf +" Beancount +au BufNewFile,BufRead *.beancount setf beancount + " BibTeX bibliography database file au BufNewFile,BufRead *.bib setf bib @@ -407,6 +413,10 @@ endif " Lynx config files au BufNewFile,BufRead lynx.cfg setf lynx +" Modula-3 configuration language (must be before *.cfg and *makefile) +au BufNewFile,BufRead *.quake,cm3.cfg setf m3quake +au BufNewFile,BufRead m3makefile,m3overrides setf m3build + " Quake au BufNewFile,BufRead *baseq[2-3]/*.cfg,*id1/*.cfg setf quake au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake @@ -586,7 +596,7 @@ au BufNewFile,BufRead *.fan,*.fwt setf fan au BufNewFile,BufRead *.factor setf factor " Fennel -autocmd BufRead,BufNewFile *.fnl setf fennel +autocmd BufRead,BufNewFile *.fnl setf fennel " Fetchmail RC file au BufNewFile,BufRead .fetchmailrc setf fetchmail @@ -630,7 +640,7 @@ au BufNewFile,BufRead *.mo,*.gdmo setf gdmo au BufNewFile,BufRead *.ged,lltxxxxx.txt setf gedcom " Gift (Moodle) -autocmd BufRead,BufNewFile *.gift setf gift +autocmd BufRead,BufNewFile *.gift setf gift " Git au BufNewFile,BufRead COMMIT_EDITMSG,MERGE_MSG,TAG_EDITMSG setf gitcommit @@ -701,7 +711,7 @@ au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc au BufNewFile,BufRead *.haml setf haml " Hamster Classic | Playground files -au BufNewFile,BufRead *.hsm setf hamster +au BufNewFile,BufRead *.hsm setf hamster " Haskell au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell @@ -1035,10 +1045,10 @@ au BufNewFile,BufRead *.mod \ setf modsim3 | \ endif -" Modula 2 (.md removed in favor of Markdown) +" Modula-2 (.md removed in favor of Markdown) au BufNewFile,BufRead *.m2,*.DEF,*.MOD,*.mi setf modula2 -" Modula 3 (.m3, .i3, .mg, .ig) +" Modula-3 (.m3, .i3, .mg, .ig) au BufNewFile,BufRead *.[mi][3g] setf modula3 " Monk @@ -1269,6 +1279,11 @@ au BufNewFile,BufRead .povrayrc setf povini " Povray, Pascal, PHP or assembly au BufNewFile,BufRead *.inc call dist#ft#FTinc() +" PowerShell +au BufNewFile,BufRead *.ps1,*.psd1,*.psm1,*.pssc setf ps1 +au BufNewFile,BufRead *.ps1xml setf ps1xml +au BufNewFile,BufRead *.cdxml,*.psc1 setf xml + " Printcap and Termcap au BufNewFile,BufRead *printcap \ let b:ptcap_type = "print" | setf ptcap @@ -1323,10 +1338,16 @@ au BufNewFile,BufRead *.pdb setf prolog " Promela au BufNewFile,BufRead *.pml setf promela +" Property Specification Language (PSL) +au BufNewFile,BufRead *.psl setf psl + " Google protocol buffers au BufNewFile,BufRead *.proto setf proto au BufNewFile,BufRead *.pbtxt setf pbtxt +" Poke +au BufNewFile,BufRead *.pk setf poke + " Protocols au BufNewFile,BufRead */etc/protocols setf protocols @@ -1391,6 +1412,9 @@ else au BufNewFile,BufRead *.rmd,*.smd setf rmd endif +" RSS looks like XML +au BufNewFile,BufRead *.rss setf xml + " R reStructuredText file if has("fname_case") au BufNewFile,BufRead *.Rrst,*.rrst,*.Srst,*.srst setf rrst @@ -1491,6 +1515,9 @@ au BufNewFile,BufRead *.sdl,*.pr setf sdl " sed au BufNewFile,BufRead *.sed setf sed +" svelte +au BufNewFile,BufRead *.svelte setf svelte + " Sieve (RFC 3028, 5228) au BufNewFile,BufRead *.siv,*.sieve setf sieve @@ -1539,11 +1566,10 @@ au BufNewFile,BufRead catalog setf catalog " Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc. " Gentoo ebuilds and Arch Linux PKGBUILDs are actually bash scripts " NOTE: Patterns ending in a star are further down, these have lower priority. -au BufNewFile,BufRead .bashrc,bashrc,bash.bashrc,.bash[_-]profile,.bash[_-]logout,.bash[_-]aliases,bash-fc[-.],*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD call dist#ft#SetFileTypeSH("bash") +au BufNewFile,BufRead .bashrc,bashrc,bash.bashrc,.bash[_-]profile,.bash[_-]logout,.bash[_-]aliases,bash-fc[-.],*.ebuild,*.bash,*.eclass,PKGBUILD,APKBUILD call dist#ft#SetFileTypeSH("bash") au BufNewFile,BufRead .kshrc,*.ksh call dist#ft#SetFileTypeSH("ksh") au BufNewFile,BufRead */etc/profile,.profile,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1)) - " Shell script (Arch Linux) or PHP file (Drupal) au BufNewFile,BufRead *.install \ if getline(1) =~ '<?php' | @@ -2217,8 +2243,11 @@ au BufNewFile,BufRead .reminders* call s:StarSetf('remind') " SGML catalog file au BufNewFile,BufRead sgml.catalog* call s:StarSetf('catalog') +" avoid doc files being recognized a shell files +au BufNewFile,BufRead */doc/{,.}bash[_-]completion{,.d,.sh}{,/*} setf text + " Shell scripts ending in a star -au BufNewFile,BufRead .bashrc*,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,bash-fc[-.]*,,PKGBUILD* call dist#ft#SetFileTypeSH("bash") +au BufNewFile,BufRead .bashrc*,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,bash-fc[-.]*,PKGBUILD*,APKBUILD*,*/{,.}bash[_-]completion{,.d,.sh}{,/*} call dist#ft#SetFileTypeSH("bash") au BufNewFile,BufRead .kshrc* call dist#ft#SetFileTypeSH("ksh") au BufNewFile,BufRead .profile* call dist#ft#SetFileTypeSH(getline(1)) 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/tcl.vim b/runtime/indent/tcl.vim index d77081841d..eafb8dd568 100644 --- a/runtime/indent/tcl.vim +++ b/runtime/indent/tcl.vim @@ -1,8 +1,8 @@ " Vim indent file " Language: Tcl -" Previous Maintainer: Nikolai Weibull <now@bitwi.se> " Latest Update: Chris Heithoff <chrisheithoff@gmail.com> -" Latest Revision: 2018-12-05 +" Previous Maintainer: Nikolai Weibull <now@bitwi.se> +" Latest Revision: 2018-12-05 if exists("b:did_indent") finish diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1db9fd43ac..d45827b0d7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -16,10 +16,7 @@ local validate = vim.validate local lsp = { protocol = protocol; - -- TODO(tjdevries): Add in the warning that `callbacks` is no longer supported. - -- util.warn_once("vim.lsp.callbacks is deprecated. Use vim.lsp.handlers instead.") handlers = default_handlers; - callbacks = default_handlers; buf = require'vim.lsp.buf'; diagnostic = require'vim.lsp.diagnostic'; @@ -41,11 +38,13 @@ lsp._request_name_to_capability = { ['textDocument/declaration'] = 'declaration'; ['textDocument/typeDefinition'] = 'type_definition'; ['textDocument/documentSymbol'] = 'document_symbol'; - ['textDocument/workspaceSymbol'] = 'workspace_symbol'; ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; ['textDocument/rename'] = 'rename'; ['textDocument/codeAction'] = 'code_action'; + ['textDocument/codeLens'] = 'code_lens'; + ['codeLens/resolve'] = 'code_lens_resolve'; ['workspace/executeCommand'] = 'execute_command'; + ['workspace/symbol'] = 'workspace_symbol'; ['textDocument/references'] = 'find_references'; ['textDocument/rangeFormatting'] = 'document_range_formatting'; ['textDocument/formatting'] = 'document_formatting'; @@ -219,8 +218,6 @@ local function validate_client_config(config) } validate { root_dir = { config.root_dir, is_dir, "directory" }; - -- TODO(remove-callbacks) - callbacks = { config.callbacks, "t", true }; handlers = { config.handlers, "t", true }; capabilities = { config.capabilities, "t", true }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; @@ -233,14 +230,14 @@ local function validate_client_config(config) before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; flags = { config.flags, "t", true }; + get_language_id = { config.get_language_id, "f", true }; } - - -- TODO(remove-callbacks) - if config.handlers and config.callbacks then - error(debug.traceback( - "Unable to configure LSP with both 'config.handlers' and 'config.callbacks'. Use 'config.handlers' exclusively." - )) - end + assert( + (not config.flags + or not config.flags.debounce_text_changes + or type(config.flags.debounce_text_changes) == 'number'), + "flags.debounce_text_changes must be nil or a number with the debounce time in milliseconds" + ) local cmd, cmd_args = lsp._cmd_parts(config.cmd) local offset_encoding = valid_encodings.UTF16 @@ -269,28 +266,202 @@ local function buf_get_full_text(bufnr) end --@private +--- Memoizes a function. On first run, the function return value is saved and +--- immediately returned on subsequent runs. If the function returns a multival, +--- only the first returned value will be memoized and returned. The function will only be run once, +--- even if it has side-effects. +--- +--@param fn (function) Function to run +--@returns (function) Memoized function +local function once(fn) + local value + local ran = false + return function(...) + if not ran then + value = fn(...) + ran = true + end + return value + end +end + + +local changetracking = {} +do + --- client_id → state + --- + --- state + --- pending_change?: function that the timer starts to trigger didChange + --- pending_changes: list of tables with the pending changesets; for incremental_sync only + --- use_incremental_sync: bool + --- buffers?: table (bufnr → lines); for incremental sync only + --- timer?: uv_timer + local state_by_client = {} + + function changetracking.init(client, bufnr) + local state = state_by_client[client.id] + if not state then + state = { + pending_changes = {}; + use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ); + } + state_by_client[client.id] = state + end + if not state.use_incremental_sync then + return + end + if not state.buffers then + state.buffers = {} + end + state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) + end + + function changetracking.reset_buf(client, bufnr) + local state = state_by_client[client.id] + if state then + changetracking._reset_timer(state) + if state.buffers then + state.buffers[bufnr] = nil + end + end + end + + function changetracking.reset(client_id) + local state = state_by_client[client_id] + if state then + state_by_client[client_id] = nil + changetracking._reset_timer(state) + end + end + + function changetracking.prepare(bufnr, firstline, new_lastline, changedtick) + local incremental_changes = function(client) + local cached_buffers = state_by_client[client.id].buffers + local lines = nvim_buf_get_lines(bufnr, 0, -1, true) + local startline = math.min(firstline + 1, math.min(#cached_buffers[bufnr], #lines)) + local endline = math.min(-(#lines - new_lastline), -1) + local incremental_change = vim.lsp.util.compute_diff( + cached_buffers[bufnr], lines, startline, endline, client.offset_encoding or 'utf-16') + cached_buffers[bufnr] = lines + return incremental_change + end + local full_changes = once(function() + return { + text = buf_get_full_text(bufnr); + }; + end) + local uri = vim.uri_from_bufnr(bufnr) + return function(client) + if client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.None then + return + end + local state = state_by_client[client.id] + local debounce = client.config.flags.debounce_text_changes + if not debounce then + local changes = state.use_incremental_sync and incremental_changes(client) or full_changes() + client.notify("textDocument/didChange", { + textDocument = { + uri = uri; + version = changedtick; + }; + contentChanges = { changes, } + }) + return + end + changetracking._reset_timer(state) + if state.use_incremental_sync then + -- This must be done immediately and cannot be delayed + -- The contents would further change and startline/endline may no longer fit + table.insert(state.pending_changes, incremental_changes(client)) + end + state.pending_change = function() + state.pending_change = nil + if client.is_stopped() then + return + end + local contentChanges + if state.use_incremental_sync then + contentChanges = state.pending_changes + state.pending_changes = {} + else + contentChanges = { full_changes(), } + end + client.notify("textDocument/didChange", { + textDocument = { + uri = uri; + version = changedtick; + }; + contentChanges = contentChanges + }) + end + state.timer = vim.loop.new_timer() + -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines + state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change)) + end + end + + function changetracking._reset_timer(state) + if state.timer then + state.timer:stop() + state.timer:close() + state.timer = nil + end + end + + --- Flushes any outstanding change notification. + function changetracking.flush(client) + local state = state_by_client[client.id] + if state then + changetracking._reset_timer(state) + if state.pending_change then + state.pending_change() + end + end + end +end + + +--@private --- Default handler for the 'textDocument/didOpen' LSP notification. --- --@param bufnr (Number) Number of the buffer, or 0 for current --@param client Client object local function text_document_did_open_handler(bufnr, client) + changetracking.init(client, bufnr) if not client.resolved_capabilities.text_document_open_close then return end if not vim.api.nvim_buf_is_loaded(bufnr) then return end + local filetype = nvim_buf_get_option(bufnr, 'filetype') + local params = { textDocument = { version = 0; uri = vim.uri_from_bufnr(bufnr); - -- TODO make sure our filetypes are compatible with languageId names. - languageId = nvim_buf_get_option(bufnr, 'filetype'); + languageId = client.config.get_language_id(bufnr, filetype); text = buf_get_full_text(bufnr); } } client.notify('textDocument/didOpen', params) util.buf_versions[bufnr] = params.textDocument.version + + -- Next chance we get, we should re-do the diagnostics + vim.schedule(function() + vim.lsp.handlers["textDocument/publishDiagnostics"]( + nil, + "textDocument/publishDiagnostics", + { + diagnostics = vim.lsp.diagnostic.get(bufnr, client.id), + uri = vim.uri_from_bufnr(bufnr), + }, + client.id + ) + end) end -- FIXME: DOC: Shouldn't need to use a dummy function @@ -412,6 +583,9 @@ end --- --@param name (string, default=client-id) Name in log messages. --- +--@param get_language_id function(bufnr, filetype) -> language ID as string. +--- Defaults to the filetype. +--- --@param offset_encoding (default="utf-16") One of "utf-8", "utf-16", --- or "utf-32" which is the encoding that the LSP server expects. Client does --- not verify this is correct. @@ -450,16 +624,10 @@ end --@param trace: "off" | "messages" | "verbose" | nil passed directly to the language --- server in the initialize request. Invalid/empty values will default to "off" --@param flags: A table with flags for the client. The current (experimental) flags are: ---- - allow_incremental_sync (bool, default false): Allow using on_line callbacks for lsp ---- ---- <pre> ---- -- In attach function for the client, you can do: ---- local custom_attach = function(client) ---- if client.config.flags then ---- client.config.flags.allow_incremental_sync = true ---- end ---- end ---- </pre> +--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits +--- - debounce_text_changes (number, default nil): Debounce didChange +--- notifications to the server by the given number in milliseconds. No debounce +--- occurs if nil --- --@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be --- fully initialized. Use `on_init` to do any actions once @@ -471,10 +639,14 @@ function lsp.start_client(config) config.flags = config.flags or {} config.settings = config.settings or {} + -- By default, get_language_id just returns the exact filetype it is passed. + -- It is possible to pass in something that will calculate a different filetype, + -- to be sent by the client. + config.get_language_id = config.get_language_id or function(_, filetype) return filetype end + local client_id = next_client_id() - -- TODO(remove-callbacks) - local handlers = config.handlers or config.callbacks or {} + local handlers = config.handlers or {} local name = config.name or tostring(client_id) local log_prefix = string.format("LSP[%s]", name) @@ -548,19 +720,21 @@ function lsp.start_client(config) function dispatch.on_exit(code, signal) active_clients[client_id] = nil uninitialized_clients[client_id] = nil - local active_buffers = {} - for bufnr, client_ids in pairs(all_buffer_active_clients) do - if client_ids[client_id] then - table.insert(active_buffers, bufnr) - end + + lsp.diagnostic.reset(client_id, all_buffer_active_clients) + changetracking.reset(client_id) + all_client_active_buffers[client_id] = nil + for _, client_ids in pairs(all_buffer_active_clients) do client_ids[client_id] = nil end - -- Buffer level cleanup - vim.schedule(function() - for _, bufnr in ipairs(active_buffers) do - lsp.diagnostic.clear(bufnr) - end - end) + + if code ~= 0 or (signal ~= 0 and signal ~= 15) then + local msg = string.format("Client %s quit with exit code %s and signal %s", client_id, code, signal) + vim.schedule(function() + vim.notify(msg, vim.log.levels.WARN) + end) + end + if config.on_exit then pcall(config.on_exit, code, signal, client_id) end @@ -579,8 +753,6 @@ function lsp.start_client(config) offset_encoding = offset_encoding; config = config; - -- TODO(remove-callbacks) - callbacks = handlers; handlers = handlers; -- for $/progress report messages = { name = name, messages = {}, progress = {}, status = {} } @@ -709,6 +881,9 @@ function lsp.start_client(config) handler = resolve_handler(method) or error(string.format("not found: %q request handler for client %q.", method, client.name)) end + -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state + changetracking.flush(client) + local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) return rpc.request(method, params, function(err, result) handler(err, method, result, client_id, bufnr) @@ -751,6 +926,14 @@ function lsp.start_client(config) --- --@param force (bool, optional) function client.stop(force) + + lsp.diagnostic.reset(client_id, all_buffer_active_clients) + changetracking.reset(client_id) + all_client_active_buffers[client_id] = nil + for _, client_ids in pairs(all_buffer_active_clients) do + client_ids[client_id] = nil + end + local handle = rpc.handle if handle:is_closing() then return @@ -797,25 +980,10 @@ function lsp.start_client(config) end --@private ---- Memoizes a function. On first run, the function return value is saved and ---- immediately returned on subsequent runs. ---- ---@param fn (function) Function to run ---@returns (function) Memoized function -local function once(fn) - local value - return function(...) - if not value then value = fn(...) end - return value - end -end - ---@private --@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) --- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do - local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; } text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size) @@ -830,60 +998,9 @@ do if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return end - util.buf_versions[bufnr] = changedtick - -- Lazy initialize these because clients may not even need them. - local incremental_changes = once(function(client) - local size_index = encoding_index[client.offset_encoding] - local length = select(size_index, old_byte_size, old_utf16_size, old_utf32_size) - local lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) - - -- This is necessary because we are specifying the full line including the - -- newline in range. Therefore, we must replace the newline as well. - if #lines > 0 then - table.insert(lines, '') - end - return { - range = { - start = { line = firstline, character = 0 }; - ["end"] = { line = lastline, character = 0 }; - }; - rangeLength = length; - text = table.concat(lines, '\n'); - }; - end) - local full_changes = once(function() - return { - text = buf_get_full_text(bufnr); - }; - end) - local uri = vim.uri_from_bufnr(bufnr) - for_each_buffer_client(bufnr, function(client, _client_id) - local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, false) - - local text_document_did_change = client.resolved_capabilities.text_document_did_change - local changes - if text_document_did_change == protocol.TextDocumentSyncKind.None then - return - --[=[ TODO(ashkan) there seem to be problems with the byte_sizes sent by - -- neovim right now so only send the full content for now. In general, we - -- can assume that servers *will* support both versions anyway, as there - -- is no way to specify the sync capability by the client. - -- See https://github.com/palantir/python-language-server/commit/cfd6675bc10d5e8dbc50fc50f90e4a37b7178821#diff-f68667852a14e9f761f6ebf07ba02fc8 for an example of pyls handling both. - --]=] - elseif not allow_incremental_sync or text_document_did_change == protocol.TextDocumentSyncKind.Full then - changes = full_changes(client) - elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then - changes = incremental_changes(client) - end - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = changedtick; - }; - contentChanges = { changes; } - }) - end) + local compute_change_and_notify = changetracking.prepare(bufnr, firstline, new_lastline, changedtick) + for_each_buffer_client(bufnr, compute_change_and_notify) end end @@ -928,16 +1045,32 @@ function lsp.buf_attach_client(bufnr, client_id) all_buffer_active_clients[bufnr] = buffer_client_ids local uri = vim.uri_from_bufnr(bufnr) - nvim_command(string.format("autocmd BufWritePost <buffer=%d> lua vim.lsp._text_document_did_save_handler(0)", bufnr)) + local buf_did_save_autocommand = [=[ + augroup lsp_c_%d_b_%d_did_save + au! + au BufWritePost <buffer=%d> lua vim.lsp._text_document_did_save_handler(0) + augroup END + ]=] + vim.api.nvim_exec(string.format(buf_did_save_autocommand, client_id, bufnr, bufnr), false) -- First time, so attach and set up stuff. vim.api.nvim_buf_attach(bufnr, false, { on_lines = text_document_did_change_handler; + on_reload = function() + local params = { textDocument = { uri = uri; } } + for_each_buffer_client(bufnr, function(client, _) + if client.resolved_capabilities.text_document_open_close then + client.notify('textDocument/didClose', params) + end + text_document_did_open_handler(bufnr, client) + end) + end; on_detach = function() local params = { textDocument = { uri = uri; } } for_each_buffer_client(bufnr, function(client, _) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end + changetracking.reset_buf(client, bufnr) end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil @@ -1016,23 +1149,12 @@ 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 @@ -1150,9 +1272,49 @@ function lsp.buf_request(bufnr, method, params, handler) return client_request_ids, _cancel_all_requests end ---- Sends a request to a server and waits for the response. +---Sends an async request for all active clients attached to the buffer. +---Executes the callback on the combined result. +---Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are +---different. +--- +--@param bufnr (number) Buffer handle, or 0 for current. +--@param method (string) LSP method name +--@param params (optional, table) Parameters to send to the server +--@param callback (function) The callback to call when all requests are finished. +-- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. +-- A map of client_id:request_result will be provided to the callback +-- +--@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`. +function lsp.buf_request_all(bufnr, method, params, callback) + local request_results = {} + local result_count = 0 + local expected_result_count = 0 + local cancel, client_request_ids + + local set_expected_result_count = once(function() + for _ in pairs(client_request_ids) do + expected_result_count = expected_result_count + 1 + end + end) + + local function _sync_handler(err, _, result, client_id) + request_results[client_id] = { error = err, result = result } + result_count = result_count + 1 + set_expected_result_count() + + if result_count >= expected_result_count then + callback(request_results) + end + end + + client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) + + return cancel +end + +--- Sends a request to all server and waits for the response of all of them. --- ---- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting the result. +--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result. --- Parameters are the same as |vim.lsp.buf_request()| but the return result is --- different. Wait maximum of {timeout_ms} (default 100) ms. --- @@ -1166,26 +1328,21 @@ end --- returns `(nil, err)` where `err` is a string describing the failure --- reason. function lsp.buf_request_sync(bufnr, method, params, timeout_ms) - local request_results = {} - local result_count = 0 - local function _sync_handler(err, _, result, client_id) - request_results[client_id] = { error = err, result = result } - result_count = result_count + 1 - end - local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler) - local expected_result_count = 0 - for _ in pairs(client_request_ids) do - expected_result_count = expected_result_count + 1 - end + local request_results + + local cancel = lsp.buf_request_all(bufnr, method, params, function(it) + request_results = it + end) local wait_result, reason = vim.wait(timeout_ms or 100, function() - return result_count >= expected_result_count + return request_results ~= nil end, 10) if not wait_result then cancel() return nil, wait_result_reason[reason] end + return request_results end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 00219b6d98..31116985e2 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -310,15 +310,21 @@ function M.workspace_symbol(query) request('workspace/symbol', params) end ---- Send request to server to resolve document highlights for the ---- current text document position. This request can be associated ---- to key mapping or to events such as `CursorHold`, eg: +--- Send request to the server to resolve document highlights for the current +--- text document position. This request can be triggered by a key mapping or +--- by events such as `CursorHold`, eg: --- --- <pre> --- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] --- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] --- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] --- </pre> +--- +--- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups +--- to be defined or you won't be able to see the actual highlights. +--- |LspReferenceText| +--- |LspReferenceRead| +--- |LspReferenceWrite| function M.document_highlight() local params = util.make_position_params() request('textDocument/documentHighlight', params) diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua deleted file mode 100644 index 1da92b900d..0000000000 --- a/runtime/lua/vim/lsp/callbacks.lua +++ /dev/null @@ -1,4 +0,0 @@ -local util = require 'vim.lsp.util' - -util._warn_once("require('vim.lsp.callbacks') is deprecated. Use vim.lsp.handlers instead.") -return require('vim.lsp.handlers') diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index a625098bab..e6132e78bf 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -264,10 +264,16 @@ local function set_diagnostic_cache(diagnostics, bufnr, client_id) -- The diagnostic's severity. Can be omitted. If omitted it is up to the -- client to interpret diagnostics as error, warning, info or hint. -- TODO: Replace this with server-specific heuristics to infer severity. + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) for _, diagnostic in ipairs(diagnostics) do if diagnostic.severity == nil then diagnostic.severity = DiagnosticSeverity.Error end + -- Account for servers that place diagnostics on terminating newline + if buf_line_count > 0 then + local start = diagnostic.range.start + start.line = math.min(start.line, buf_line_count - 1) + end end diagnostic_cache[bufnr][client_id] = diagnostics @@ -1134,14 +1140,14 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) local message_lines = vim.split(diagnostic.message, '\n', true) table.insert(lines, prefix..message_lines[1]) - table.insert(highlights, {#prefix + 1, hiname}) + table.insert(highlights, {#prefix, hiname}) for j = 2, #message_lines do table.insert(lines, message_lines[j]) table.insert(highlights, {0, hiname}) end end - local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext') + local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts) for i, hi in ipairs(highlights) do local prefixlen, hiname = unpack(hi) -- Start highlight after the prefix @@ -1158,6 +1164,24 @@ local loclist_type_map = { [DiagnosticSeverity.Hint] = 'I', } + +--- Clear diagnotics and diagnostic cache +--- +--- Handles saving diagnostics from multiple clients in the same buffer. +---@param client_id number +---@param buffer_client_map table map of buffers to active clients +function M.reset(client_id, buffer_client_map) + buffer_client_map = vim.deepcopy(buffer_client_map) + vim.schedule(function() + for bufnr, client_ids in pairs(buffer_client_map) do + if client_ids[client_id] then + clear_diagnostic_cache(bufnr, client_id) + M.clear(bufnr, client_id) + end + end + end) +end + --- Sets the location list ---@param opts table|nil Configuration table. Keys: --- - {open_loclist}: (boolean, default true) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 7eac3febd9..525ec4ce5b 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -13,7 +13,7 @@ local M = {} --- Writes to error buffer. --@param ... (table of strings) Will be concatenated before being written local function err_message(...) - api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) + vim.notify(table.concat(vim.tbl_flatten{...}), vim.log.levels.ERROR) api.nvim_command("redraw") end @@ -26,7 +26,7 @@ end -- @msg of type ProgressParams -- Basically a token of type number/string -local function progress_callback(_, _, params, client_id) +local function progress_handler(_, _, params, client_id) local client = vim.lsp.get_client_by_id(client_id) local client_name = client and client.name or string.format("id=%d", client_id) if not client then @@ -62,7 +62,7 @@ local function progress_callback(_, _, params, client_id) end --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress -M['$/progress'] = progress_callback +M['$/progress'] = progress_handler --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create M['window/workDoneProgress/create'] = function(_, _, params, client_id) @@ -245,9 +245,22 @@ M['textDocument/completion'] = function(_, _, result) vim.fn.complete(textMatch+1, matches) end ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover -M['textDocument/hover'] = function(_, method, result) - util.focusable_float(method, function() +--- |lsp-handler| for the method "textDocument/hover" +--- <pre> +--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( +--- vim.lsp.handlers.hover, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single" +--- } +--- ) +--- </pre> +---@param config table Configuration table. +--- - border: (default=nil) +--- - Add borders to the floating window +--- - See |vim.api.nvim_open_win()| +function M.hover(_, method, result, _, _, config) + config = config or {} + local bufnr, winnr = util.focusable_float(method, function() if not (result and result.contents) then -- return { 'No information available' } return @@ -259,13 +272,17 @@ M['textDocument/hover'] = function(_, method, result) return end local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, { - pad_left = 1; pad_right = 1; + border = config.border }) util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr) return bufnr, winnr end) + return bufnr, winnr end +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover +M['textDocument/hover'] = M.hover + --@private --- Jumps to a location. Used as a handler for multiple LSP methods. --@param _ (not used) @@ -303,8 +320,21 @@ M['textDocument/typeDefinition'] = location_handler --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation M['textDocument/implementation'] = location_handler ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp -M['textDocument/signatureHelp'] = function(_, method, result) +--- |lsp-handler| for the method "textDocument/signatureHelp" +--- <pre> +--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( +--- vim.lsp.handlers.signature_help, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single" +--- } +--- ) +--- </pre> +---@param config table Configuration table. +--- - border: (default=nil) +--- - Add borders to the floating window +--- - See |vim.api.nvim_open_win()| +function M.signature_help(_, method, result, _, bufnr, config) + config = config or {} -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore if not (result and result.signatures and result.signatures[1]) then @@ -317,11 +347,16 @@ M['textDocument/signatureHelp'] = function(_, method, result) print('No signature help available') return end - util.focusable_preview(method, function() - return lines, util.try_trim_markdown_code_blocks(lines) + local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + local p_bufnr, _ = util.focusable_preview(method, function() + return lines, util.try_trim_markdown_code_blocks(lines), config end) + api.nvim_buf_set_option(p_bufnr, 'syntax', syntax) end +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp +M['textDocument/signatureHelp'] = M.signature_help + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight M['textDocument/documentHighlight'] = function(_, _, result, _, bufnr, _) if not result then return end @@ -409,7 +444,7 @@ for k, fn in pairs(M) do }) if err then - error(tostring(err)) + return err_message(tostring(err)) end return fn(err, method, params, client_id, bufnr, config) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index b6e91e37b9..331e980e67 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -10,13 +10,7 @@ local log = {} -- Can be used to lookup the number from the name or the name from the number. -- Levels by name: 'trace', 'debug', 'info', 'warn', 'error' -- Level numbers begin with 'trace' at 0 -log.levels = { - TRACE = 0; - DEBUG = 1; - INFO = 2; - WARN = 3; - ERROR = 4; -} +log.levels = vim.log.levels -- Default log level is warn. local current_log_level = log.levels.WARN @@ -38,6 +32,8 @@ do vim.fn.mkdir(vim.fn.stdpath('cache'), "p") local logfile = assert(io.open(logfilename, "a+")) + -- Start message for logging + logfile:write(string.format("[ START ] %s ] LSP logging initiated\n", os.date(log_date_format))) for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. log[level] = levelnr @@ -74,8 +70,6 @@ do logfile:flush() end end - -- Add some space to make it easier to distinguish different neovim runs. - logfile:write("\n") end -- This is put here on purpose after the loop above so that it doesn't diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 3e111c154a..7e43eb84de 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -749,6 +749,9 @@ function protocol.make_client_capabilities() }; workspaceFolders = true; applyEdit = true; + workspaceEdit = { + resourceOperations = {'rename', 'create', 'delete',}, + }; }; callHierarchy = { dynamicRegistration = false; @@ -938,7 +941,7 @@ function protocol.resolve_capabilities(server_capabilities) text_document_did_change = textDocumentSync; text_document_will_save = false; text_document_will_save_wait_until = false; - text_document_save = false; + text_document_save = true; text_document_save_include_text = false; } elseif type(textDocumentSync) == 'table' then @@ -975,6 +978,16 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.rename = true end + if server_capabilities.codeLensProvider == nil then + general_properties.code_lens = false + general_properties.code_lens_resolve = false + elseif type(server_capabilities.codeLensProvider) == 'table' then + general_properties.code_lens = true + general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false + else + error("The server sent invalid codeLensProvider") + end + if server_capabilities.codeActionProvider == nil then general_properties.code_action = false elseif type(server_capabilities.codeActionProvider) == 'boolean' diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 90f51dfc5a..1aa8326514 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -315,8 +315,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) dispatchers = { dispatchers, 't', true }; } - if not (vim.fn.executable(cmd) == 1) then - error(string.format("The given command %q is not executable.", cmd)) + if extra_spawn_params and extra_spawn_params.cwd then + assert(is_dir(extra_spawn_params.cwd), "cwd must be a directory") + elseif not (vim.fn.executable(cmd) == 1) then + error(string.format("The given command %q is not executable.", cmd)) end if dispatchers then local user_dispatchers = dispatchers @@ -370,12 +372,12 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) } if extra_spawn_params then spawn_params.cwd = extra_spawn_params.cwd - if spawn_params.cwd then - assert(is_dir(spawn_params.cwd), "cwd must be a directory") - end spawn_params.env = env_merge(extra_spawn_params.env) end handle, pid = uv.spawn(cmd, spawn_params, onexit) + if handle == nil then + error(string.format("start `%s` failed: %s", cmd, pid)) + end end --@private @@ -386,7 +388,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) --@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. local function encode_and_send(payload) local _ = log.debug() and log.debug("rpc.send.payload", payload) - if handle:is_closing() then return false end + if handle == nil or handle:is_closing() then return false end -- TODO(ashkan) remove this once we have a Lua json_encode schedule(function() local encoded = assert(json_encode(payload)) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index e33e0109b6..71ec85381b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -18,13 +18,39 @@ end local M = {} --- TODO(remove-callbacks) -M.diagnostics_by_buf = setmetatable({}, { - __index = function(_, bufnr) - warn_once("diagnostics_by_buf is deprecated. Use 'vim.lsp.diagnostic.get'") - return vim.lsp.diagnostic.get(bufnr) +local default_border = { + {"", "NormalFloat"}, + {"", "NormalFloat"}, + {"", "NormalFloat"}, + {" ", "NormalFloat"}, + {"", "NormalFloat"}, + {"", "NormalFloat"}, + {"", "NormalFloat"}, + {" ", "NormalFloat"}, +} + +--@private +-- Check the border given by opts or the default border for the additional +-- size it adds to a float. +--@returns size of border in height and width +local function get_border_size(opts) + local border = opts and opts.border or default_border + local height = 0 + local width = 0 + + if type(border) == 'string' then + -- 'single', 'double', etc. + height = 2 + width = 2 + else + height = height + vim.fn.strdisplaywidth(border[2][1]) -- top + height = height + vim.fn.strdisplaywidth(border[6][1]) -- bottom + width = width + vim.fn.strdisplaywidth(border[4][1]) -- right + width = width + vim.fn.strdisplaywidth(border[8][1]) -- left end -}) + + return { height = height, width = width } +end --@private local function split_lines(value) @@ -211,7 +237,7 @@ function M.apply_text_edits(text_edits, bufnr) local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol') local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1 - if set_eol and #lines[#lines] ~= 0 then + if set_eol and (#lines == 0 or #lines[#lines] ~= 0) then table.insert(lines, '') end @@ -234,6 +260,177 @@ end -- function M.glob_to_regex(glob) -- end +--@private +--- Finds the first line and column of the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@returns (int, int) start_line_idx and start_col_idx of range +local function first_difference(old_lines, new_lines, start_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 1, 1 end + if not start_line_idx then + for i = 1, line_count do + start_line_idx = i + if old_lines[start_line_idx] ~= new_lines[start_line_idx] then + break + end + end + end + local old_line = old_lines[start_line_idx] + local new_line = new_lines[start_line_idx] + local length = math.min(#old_line, #new_line) + local start_col_idx = 1 + while start_col_idx <= length do + if string.sub(old_line, start_col_idx, start_col_idx) ~= string.sub(new_line, start_col_idx, start_col_idx) then + break + end + start_col_idx = start_col_idx + 1 + end + return start_line_idx, start_col_idx +end + + +--@private +--- Finds the last line and column of the differences between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@param start_char integer First different character idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function last_difference(old_lines, new_lines, start_char, end_line_idx) + local line_count = math.min(#old_lines, #new_lines) + if line_count == 0 then return 0,0 end + if not end_line_idx then + end_line_idx = -1 + end + for i = end_line_idx, -line_count, -1 do + if old_lines[#old_lines + i + 1] ~= new_lines[#new_lines + i + 1] then + end_line_idx = i + break + end + end + local old_line + local new_line + if end_line_idx <= -line_count then + end_line_idx = -line_count + old_line = string.sub(old_lines[#old_lines + end_line_idx + 1], start_char) + new_line = string.sub(new_lines[#new_lines + end_line_idx + 1], start_char) + else + old_line = old_lines[#old_lines + end_line_idx + 1] + new_line = new_lines[#new_lines + end_line_idx + 1] + end + local old_line_length = #old_line + local new_line_length = #new_line + local length = math.min(old_line_length, new_line_length) + local end_col_idx = -1 + while end_col_idx >= -length do + local old_char = string.sub(old_line, old_line_length + end_col_idx + 1, old_line_length + end_col_idx + 1) + local new_char = string.sub(new_line, new_line_length + end_col_idx + 1, new_line_length + end_col_idx + 1) + if old_char ~= new_char then + break + end + end_col_idx = end_col_idx - 1 + end + return end_line_idx, end_col_idx + +end + +--@private +--- Get the text of the range defined by start and end line/column +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns string text extracted from defined region +local function extract_text(lines, start_line, start_char, end_line, end_char) + if start_line == #lines + end_line + 1 then + if end_line == 0 then return '' end + local line = lines[start_line] + local length = #line + end_char - start_char + return string.sub(line, start_char, start_char + length + 1) + end + local result = string.sub(lines[start_line], start_char) .. '\n' + for line_idx = start_line + 1, #lines + end_line do + result = result .. lines[line_idx] .. '\n' + end + if end_line ~= 0 then + local line = lines[#lines + end_line + 1] + local length = #line + end_char + 1 + result = result .. string.sub(line, 1, length) + end + return result +end + +--@private +--- Compute the length of the substituted range +--@param lines table list of lines +--@param start_char integer First different character idx of range +--@param end_char integer Last different character idx of range +--@param start_line integer First different line idx of range +--@param end_line integer Last different line idx of range +--@returns (int, int) end_line_idx and end_col_idx of range +local function compute_length(lines, start_line, start_char, end_line, end_char) + local adj_end_line = #lines + end_line + 1 + local adj_end_char + if adj_end_line > #lines then + adj_end_char = end_char - 1 + else + adj_end_char = #lines[adj_end_line] + end_char + end + if start_line == adj_end_line then + return adj_end_char - start_char + 1 + end + local result = #lines[start_line] - start_char + 1 + for line = start_line + 1, adj_end_line -1 do + result = result + #lines[line] + 1 + end + result = result + adj_end_char + 1 + return result +end + +--- Returns the range table for the difference between old and new lines +--@param old_lines table list of lines +--@param new_lines table list of lines +--@param start_line_idx int line to begin search for first difference +--@param end_line_idx int line to begin search for last difference +--@param offset_encoding string encoding requested by language server +--@returns table start_line_idx and start_col_idx of range +function M.compute_diff(old_lines, new_lines, start_line_idx, end_line_idx, offset_encoding) + local start_line, start_char = first_difference(old_lines, new_lines, start_line_idx) + local end_line, end_char = last_difference(vim.list_slice(old_lines, start_line, #old_lines), + vim.list_slice(new_lines, start_line, #new_lines), start_char, end_line_idx) + local text = extract_text(new_lines, start_line, start_char, end_line, end_char) + local length = compute_length(old_lines, start_line, start_char, end_line, end_char) + + local adj_end_line = #old_lines + end_line + local adj_end_char + if end_line == 0 then + adj_end_char = 0 + else + adj_end_char = #old_lines[#old_lines + end_line + 1] + end_char + 1 + end + + local _ + if offset_encoding == "utf-16" then + _, start_char = vim.str_utfindex(old_lines[start_line], start_char - 1) + _, end_char = vim.str_utfindex(old_lines[#old_lines + end_line + 1], adj_end_char) + else + start_char = start_char - 1 + end_char = adj_end_char + end + + local result = { + range = { + start = { line = start_line - 1, character = start_char}, + ["end"] = { line = adj_end_line, character = end_char} + }, + text = text, + rangeLength = length + 1, + } + + return result +end + --- Can be used to extract the completion items from a --- `textDocument/completion` request, which may return one of --- `CompletionItem[]`, `CompletionList` or null. @@ -273,6 +470,7 @@ function M.apply_text_document_edit(text_document_edit, index) -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier if should_check_version and (text_document.version + and text_document.version > 0 and M.buf_versions[bufnr] and M.buf_versions[bufnr] > text_document.version) then print("Buffer ", text_document.uri, " newer than edits.") @@ -367,13 +565,13 @@ end --- precedence is as follows: textEdit.newText > insertText > label --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) - if item.textEdit ~= nil and item.textEdit.newText ~= nil then + if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= "" then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then return item.textEdit.newText else return M.parse_snippet(item.textEdit.newText) end - elseif item.insertText ~= nil then + elseif item.insertText ~= nil and item.insertText ~= "" then if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then return item.insertText else @@ -461,6 +659,62 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end + +--- Rename old_fname to new_fname +-- +--@param opts (table) +-- overwrite? bool +-- ignoreIfExists? bool +function M.rename(old_fname, new_fname, opts) + opts = opts or {} + local bufnr = vim.fn.bufadd(old_fname) + vim.fn.bufload(bufnr) + local target_exists = vim.loop.fs_stat(new_fname) ~= nil + if target_exists and not opts.overwrite or opts.ignoreIfExists then + vim.notify('Rename target already exists. Skipping rename.') + return + end + local ok, err = os.rename(old_fname, new_fname) + assert(ok, err) + api.nvim_buf_call(bufnr, function() + vim.cmd('saveas! ' .. vim.fn.fnameescape(new_fname)) + end) +end + + +local function create_file(change) + local opts = change.options or {} + -- from spec: Overwrite wins over `ignoreIfExists` + local fname = vim.uri_to_fname(change.uri) + if not opts.ignoreIfExists or opts.overwrite then + local file = io.open(fname, 'w') + file:close() + end + vim.fn.bufadd(fname) +end + + +local function delete_file(change) + local opts = change.options or {} + local fname = vim.uri_to_fname(change.uri) + local stat = vim.loop.fs_stat(fname) + if opts.ignoreIfNotExists and not stat then + return + end + assert(stat, "Cannot delete not existing file or folder " .. fname) + local flags + if stat and stat.type == 'directory' then + flags = opts.recursive and 'rf' or 'd' + else + flags = '' + end + local bufnr = vim.fn.bufadd(fname) + local result = tonumber(vim.fn.delete(fname, flags)) + assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat)) + api.nvim_buf_delete(bufnr, { force = true }) +end + + --- Applies a `WorkspaceEdit`. --- --@param workspace_edit (table) `WorkspaceEdit` @@ -468,8 +722,17 @@ end function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do - if change.kind then - -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile + if change.kind == "rename" then + M.rename( + vim.uri_to_fname(change.oldUri), + vim.uri_to_fname(change.newUri), + change.options + ) + elseif change.kind == 'create' then + create_file(change) + elseif change.kind == 'delete' then + delete_file(change) + elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else M.apply_text_document_edit(change, idx) @@ -628,7 +891,7 @@ function M.make_floating_popup_options(width, height, opts) else anchor = anchor..'S' height = math.min(lines_above, height) - row = 0 + row = -get_border_size(opts).height end if vim.fn.wincol() + width <= api.nvim_get_option('columns') then @@ -647,6 +910,7 @@ function M.make_floating_popup_options(width, height, opts) row = row + (opts.offset_y or 0), style = 'minimal', width = width, + border = opts.border or default_border, } end @@ -695,8 +959,8 @@ function M.preview_location(location) end local range = location.targetRange or location.range local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false) - local filetype = api.nvim_buf_get_option(bufnr, 'filetype') - return M.open_floating_preview(contents, filetype) + local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + return M.open_floating_preview(contents, syntax) end --@private @@ -726,7 +990,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 @@ -753,27 +1017,20 @@ function M.focusable_preview(unique_name, fn) end) end ---- Trims empty lines from input and pad left and right with spaces +--- Trims empty lines from input and pad top and bottom with empty lines --- ---@param contents table of lines to trim and pad ---@param opts dictionary with optional fields ---- - pad_left number of columns to pad contents at left (default 1) ---- - pad_right number of columns to pad contents at right (default 1) --- - pad_top number of lines to pad contents at top (default 0) --- - pad_bottom number of lines to pad contents at bottom (default 0) ---@return contents table of trimmed and padded lines -function M._trim_and_pad(contents, opts) +function M._trim(contents, opts) validate { contents = { contents, 't' }; opts = { opts, 't', true }; } opts = opts or {} - local left_padding = (" "):rep(opts.pad_left or 1) - local right_padding = (" "):rep(opts.pad_right or 1) contents = M.trim_empty_lines(contents) - for i, line in ipairs(contents) do - contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding) - end if opts.pad_top then for _ = 1, opts.pad_top do table.insert(contents, 1, "") @@ -850,8 +1107,8 @@ function M.fancy_floating_markdown(contents, opts) end end end - -- Clean up and add padding - stripped = M._trim_and_pad(stripped, opts) + -- Clean up + stripped = M._trim(stripped, opts) -- Compute size of float needed to show (wrapped) lines opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) @@ -881,8 +1138,10 @@ function M.fancy_floating_markdown(contents, opts) -- This is because the syntax command doesn't accept a target. local cwin = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(winnr) + api.nvim_win_set_option(winnr, 'conceallevel', 2) + api.nvim_win_set_option(winnr, 'concealcursor', 'n') - vim.cmd("ownsyntax markdown") + vim.cmd("ownsyntax lsp_markdown") local idx = 1 --@private local function apply_syntax_to_region(ft, start, finish) @@ -952,6 +1211,20 @@ function M._make_floating_popup_size(contents, opts) width = math.max(line_widths[i], width) end end + + local border_width = get_border_size(opts).width + local screen_width = api.nvim_win_get_width(0) + width = math.min(width, screen_width) + + -- make sure borders are always inside the screen + if width + border_width > screen_width then + width = width - (width + border_width - screen_width) + end + + if wrap_at and wrap_at > width then + wrap_at = width + end + if max_width then width = math.min(width, max_width) wrap_at = math.min(wrap_at or max_width, max_width) @@ -983,7 +1256,7 @@ end --- Shows contents in a floating window. --- --@param contents table of lines to show in window ---@param filetype string of filetype to set for opened buffer +--@param syntax string of syntax to set for opened buffer --@param opts dictionary with optional fields -- - height of floating window -- - width of floating window @@ -996,28 +1269,28 @@ end -- - pad_bottom number of lines to pad contents at bottom --@returns bufnr,winnr buffer and window number of the newly created floating ---preview window -function M.open_floating_preview(contents, filetype, opts) +function M.open_floating_preview(contents, syntax, opts) validate { contents = { contents, 't' }; - filetype = { filetype, 's', true }; + syntax = { syntax, 's', true }; opts = { opts, 't', true }; } opts = opts or {} -- Clean up input: trim empty lines from the end, pad - contents = M._trim_and_pad(contents, opts) + contents = M._trim(contents, opts) -- Compute size of float needed to show (wrapped) lines opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) local width, height = M._make_floating_popup_size(contents, opts) local floating_bufnr = api.nvim_create_buf(false, true) - if filetype then - api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype) + if syntax then + api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax) end local float_option = M.make_floating_popup_options(width, height, opts) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) - if filetype == 'markdown' then + if syntax == 'markdown' then api.nvim_win_set_option(floating_winnr, 'conceallevel', 2) end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) @@ -1027,78 +1300,6 @@ function M.open_floating_preview(contents, filetype, opts) return floating_bufnr, floating_winnr end --- TODO(remove-callbacks) -do - --@deprecated - function M.get_severity_highlight_name(severity) - warn_once("vim.lsp.util.get_severity_highlight_name is deprecated.") - return vim.lsp.diagnostic._get_severity_highlight_name(severity) - end - - --@deprecated - function M.buf_clear_diagnostics(bufnr, client_id) - warn_once("buf_clear_diagnostics is deprecated. Use vim.lsp.diagnostic.clear") - return vim.lsp.diagnostic.clear(bufnr, client_id) - end - - --@deprecated - function M.get_line_diagnostics() - warn_once("get_line_diagnostics is deprecated. Use vim.lsp.diagnostic.get_line_diagnostics") - - local bufnr = api.nvim_get_current_buf() - local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - - return vim.lsp.diagnostic.get_line_diagnostics(bufnr, line_nr) - end - - --@deprecated - function M.show_line_diagnostics() - warn_once("show_line_diagnostics is deprecated. Use vim.lsp.diagnostic.show_line_diagnostics") - - local bufnr = api.nvim_get_current_buf() - local line_nr = api.nvim_win_get_cursor(0)[1] - 1 - - return vim.lsp.diagnostic.show_line_diagnostics(bufnr, line_nr) - end - - --@deprecated - function M.buf_diagnostics_save_positions(bufnr, diagnostics, client_id) - warn_once("buf_diagnostics_save_positions is deprecated. Use vim.lsp.diagnostic.save") - return vim.lsp.diagnostic.save(diagnostics, bufnr, client_id) - end - - --@deprecated - function M.buf_diagnostics_get_positions(bufnr, client_id) - warn_once("buf_diagnostics_get_positions is deprecated. Use vim.lsp.diagnostic.get") - return vim.lsp.diagnostic.get(bufnr, client_id) - end - - --@deprecated - function M.buf_diagnostics_underline(bufnr, diagnostics, client_id) - warn_once("buf_diagnostics_underline is deprecated. Use 'vim.lsp.diagnostic.set_underline'") - return vim.lsp.diagnostic.set_underline(diagnostics, bufnr, client_id) - end - - --@deprecated - function M.buf_diagnostics_virtual_text(bufnr, diagnostics, client_id) - warn_once("buf_diagnostics_virtual_text is deprecated. Use 'vim.lsp.diagnostic.set_virtual_text'") - return vim.lsp.diagnostic.set_virtual_text(diagnostics, bufnr, client_id) - end - - --@deprecated - function M.buf_diagnostics_signs(bufnr, diagnostics, client_id) - warn_once("buf_diagnostics_signs is deprecated. Use 'vim.lsp.diagnostic.set_signs'") - return vim.lsp.diagnostic.set_signs(diagnostics, bufnr, client_id) - end - - --@deprecated - function M.buf_diagnostics_count(kind, client_id) - warn_once("buf_diagnostics_count is deprecated. Use 'vim.lsp.diagnostic.get_count'") - return vim.lsp.diagnostic.get_count(vim.api.nvim_get_current_buf(), client_id, kind) - end - -end - do --[[ References ]] local reference_ns = api.nvim_create_namespace("vim_lsp_references") diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 998e04f568..0a663628a5 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -400,6 +400,20 @@ function vim.tbl_count(t) return count end +--- Creates a copy of a table containing only elements from start to end (inclusive) +--- +--@param list table table +--@param start integer Start range of slice +--@param finish integer End range of slice +--@returns Copy of table sliced from start to finish (inclusive) +function vim.list_slice(list, start, finish) + local new_list = {} + for i = start or 1, finish or #list do + new_list[#new_list+1] = list[i] + end + return new_list +end + --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- --@see https://www.lua.org/pil/20.2.html diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 38ac182e32..f223c7b8c8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -10,14 +10,15 @@ local parsers = {} local M = vim.tbl_extend("error", query, language) +M.language_version = vim._ts_get_language_version() + setmetatable(M, { __index = function (t, k) - if k == "TSHighlighter" then - a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter") + if k == "highlighter" then t[k] = require'vim.treesitter.highlighter' return t[k] - elseif k == "highlighter" then - t[k] = require'vim.treesitter.highlighter' + elseif k == "language" then + t[k] = require"vim.treesitter.language" return t[k] end end @@ -44,13 +45,18 @@ function M._create_parser(bufnr, lang, opts) self:_on_bytes(...) end - local function detach_cb() + local function detach_cb(_, ...) if parsers[bufnr] == self then parsers[bufnr] = nil end + self:_on_detach(...) + end + + local function reload_cb(_, ...) + self:_on_reload(...) end - a.nvim_buf_attach(self:source(), 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, on_reload=reload_cb, preview=true}) self:parse() diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua new file mode 100644 index 0000000000..dd0b11a6c7 --- /dev/null +++ b/runtime/lua/vim/treesitter/health.lua @@ -0,0 +1,34 @@ +local M = {} +local ts = vim.treesitter + +function M.list_parsers() + return vim.api.nvim_get_runtime_file('parser/*', true) +end + +function M.check_health() + local report_info = vim.fn['health#report_info'] + local report_ok = vim.fn['health#report_ok'] + local report_error = vim.fn['health#report_error'] + local parsers = M.list_parsers() + + report_info(string.format("Runtime ABI version : %d", ts.language_version)) + + for _, parser in pairs(parsers) do + local parsername = vim.fn.fnamemodify(parser, ":t:r") + + local is_loadable, ret = pcall(ts.language.require_language, parsername) + + if not is_loadable then + report_error(string.format("Impossible to load parser for %s: %s", parsername, ret)) + elseif ret then + local lang = ts.language.inspect_language(parsername) + report_ok(string.format("Loaded parser for %s: ABI version %d", + parsername, lang._abi_version)) + else + report_error(string.format("Unable to load parser for %s", parsername)) + end + end +end + +return M + diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 275e960e28..fe7e1052c9 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -12,6 +12,16 @@ TSHighlighterQuery.__index = TSHighlighterQuery local ns = a.nvim_create_namespace("treesitter/highlighter") +local _default_highlights = {} +local _link_default_highlight_once = function(from, to) + if not _default_highlights[from] then + _default_highlights[from] = true + vim.cmd(string.format("highlight default link %s %s", from, to)) + end + + return from +end + -- These are conventions defined by nvim-treesitter, though it -- needs to be user extensible also. TSHighlighter.hl_map = { @@ -70,9 +80,12 @@ function TSHighlighterQuery.new(lang, query_string) self.hl_cache = setmetatable({}, { __index = function(table, capture) - local hl = self:get_hl_from_capture(capture) - rawset(table, capture, hl) + local hl, is_vim_highlight = self:_get_hl_from_capture(capture) + if not is_vim_highlight then + hl = _link_default_highlight_once(lang .. hl, hl) + end + rawset(table, capture, hl) return hl end }) @@ -90,16 +103,16 @@ function TSHighlighterQuery:query() return self._query end -function TSHighlighterQuery:get_hl_from_capture(capture) +--- Get the hl from capture. +--- Returns a tuple { highlight_name: string, is_builtin: bool } +function TSHighlighterQuery:_get_hl_from_capture(capture) local name = self._query.captures[capture] if is_highlight_name(name) then -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1] + return vim.split(name, '.', true)[1], true else - -- Default to false to avoid recomputing - local hl = TSHighlighter.hl_map[name] - return hl and a.nvim_get_hl_id_by_name(hl) or 0 + return TSHighlighter.hl_map[name] or name, false end end @@ -113,8 +126,9 @@ function TSHighlighter.new(tree, opts) opts = opts or {} self.tree = tree tree:register_cbs { - on_changedtree = function(...) self:on_changedtree(...) end, - on_bytes = function(...) self:on_bytes(...) end + on_changedtree = function(...) self:on_changedtree(...) end; + on_bytes = function(...) self:on_bytes(...) end; + on_detach = function(...) self:on_detach(...) end; } self.bufnr = tree:source() @@ -176,6 +190,10 @@ function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end +function TSHighlighter:on_detach() + self:destroy() +end + function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1) @@ -203,6 +221,9 @@ local function on_line_impl(self, buf, line) local state = self:get_highlight_state(tstree) local highlighter_query = self:get_query(tree:lang()) + -- Some injected languages may not have highlight queries. + if not highlighter_query:query() then return end + if state.iter == nil then state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index d60cd2d0c7..eed28e0e41 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -2,12 +2,12 @@ local a = vim.api local M = {} ---- Asserts that the provided language is installed, and optionnaly provide a path for the parser +--- Asserts that the provided language is installed, and optionally provide a path for the parser -- -- Parsers are searched in the `parser` runtime directory. -- -- @param lang The language the parser should parse --- @param path Optionnal path the parser is located at +-- @param path Optional path the parser is located at -- @param silent Don't throw an error if language not found function M.require_language(lang, path, silent) if vim._ts_has_language(lang) then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index c864fe5878..2f5aeb0710 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -6,35 +6,41 @@ local LanguageTree = {} LanguageTree.__index = LanguageTree -- Represents a single treesitter parser for a language. --- The language can contain child languages with in it's range, +-- The language can contain child languages with in its range, -- hence the tree. -- -- @param source Can be a bufnr or a string of text to parse -- @param lang The language this tree represents -- @param opts Options table --- @param opts.queries A table of language to injection query strings --- This is useful for overridding the built in runtime file --- searching for the injection language query per language. +-- @param opts.injections A table of language to injection query strings. +-- This is useful for overriding the built-in runtime file +-- searching for the injection language query per language. function LanguageTree.new(source, lang, opts) language.require_language(lang) opts = opts or {} - local custom_queries = opts.queries or {} + if opts.queries then + a.nvim_err_writeln("'queries' is no longer supported. Use 'injections' now") + opts.injections = opts.queries + end + + local injections = opts.injections or {} local self = setmetatable({ - _source=source, - _lang=lang, + _source = source, + _lang = lang, _children = {}, _regions = {}, _trees = {}, _opts = opts, - _injection_query = custom_queries[lang] - and query.parse_query(lang, custom_queries[lang]) + _injection_query = injections[lang] + and query.parse_query(lang, injections[lang]) or query.get_query(lang, "injections"), _valid = false, _parser = vim._create_ts_parser(lang), _callbacks = { changedtree = {}, bytes = {}, + detach = {}, child_added = {}, child_removed = {} }, @@ -44,12 +50,17 @@ function LanguageTree.new(source, lang, opts) return self end --- Invalidates this parser and all it's children -function LanguageTree:invalidate() +-- Invalidates this parser and all its children +function LanguageTree:invalidate(reload) self._valid = false + -- buffer was reloaded, reparse all trees + if reload then + self._trees = {} + end + for _, child in ipairs(self._children) do - child:invalidate() + child:invalidate(reload) end end @@ -97,7 +108,7 @@ function LanguageTree:parse() self._trees = {} -- If there are no ranges, set to an empty list - -- so the included ranges in the parser ar cleared. + -- so the included ranges in the parser are cleared. if self._regions and #self._regions > 0 then for i, ranges in ipairs(self._regions) do local old_tree = old_trees[i] @@ -214,7 +225,7 @@ function LanguageTree:remove_child(lang) end end --- Destroys this language tree and all it's children. +-- Destroys this language tree and all its children. -- Any cleanup logic should be performed here. -- Note, this DOES NOT remove this tree from a parent. -- `remove_child` must be called on the parent to remove it. @@ -241,19 +252,19 @@ end -- -- Note, this call invalidates the tree and requires it to be parsed again. -- --- @param regions A list of regions this tree should manange and parse. +-- @param regions A list of regions this tree should manage and parse. function LanguageTree:set_included_regions(regions) - -- Transform the tables from 4 element long to 6 element long (with byte offset) - for _, region in ipairs(regions) do - for i, range in ipairs(region) do - if type(range) == "table" and #range == 4 then - -- TODO(vigoux): I don't think string parsers are useful for now - if type(self._source) == "number" then + -- TODO(vigoux): I don't think string parsers are useful for now + if type(self._source) == "number" then + -- Transform the tables from 4 element long to 6 element long (with byte offset) + for _, region in ipairs(regions) do + for i, range in ipairs(region) do + if type(range) == "table" and #range == 4 then local start_row, start_col, end_row, end_col = unpack(range) -- Easy case, this is a buffer parser -- TODO(vigoux): proper byte computation here, and account for EOL ? - local start_byte = a.nvim_buf_get_offset(self.bufnr, start_row) + start_col - local end_byte = a.nvim_buf_get_offset(self.bufnr, end_row) + end_col + local start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col + local end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte } end @@ -291,33 +302,50 @@ function LanguageTree:_get_injections() for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do local lang = nil - local injection_node = nil - local combined = false + local ranges = {} + local combined = metadata.combined + + -- Directives can configure how injections are captured as well as actual node captures. + -- This allows more advanced processing for determining ranges and language resolution. + if metadata.content then + local content = metadata.content + + -- Allow for captured nodes to be used + if type(content) == "number" then + content = {match[content]} + end + + if content then + vim.list_extend(ranges, content) + end + end + + if metadata.language then + lang = metadata.language + end -- You can specify the content and language together -- using a tag with the language, for example -- @javascript for id, node in pairs(match) do - local data = metadata[id] local name = self._injection_query.captures[id] - local offset_range = data and data.offset -- Lang should override any other language tag - if name == "language" then + if name == "language" and not lang then lang = query.get_node_text(node, self._source) elseif name == "combined" then combined = true - elseif name == "content" then - injection_node = offset_range or node + elseif name == "content" and #ranges == 0 then + table.insert(ranges, node) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= "_" then - if lang == nil then + if not lang then lang = name end - if not injection_node then - injection_node = offset_range or node + if #ranges == 0 then + table.insert(ranges, node) end end end @@ -331,21 +359,21 @@ function LanguageTree:_get_injections() injections[tree_index][lang] = {} end - -- Key by pattern so we can either combine each node to parse in the same - -- context or treat each node independently. + -- Key this by pattern. If combined is set to true all captures of this pattern + -- will be parsed by treesitter as the same "source". + -- If combined is false, each "region" will be parsed as a single source. if not injections[tree_index][lang][pattern] then - injections[tree_index][lang][pattern] = { combined = combined, nodes = {} } + injections[tree_index][lang][pattern] = { combined = combined, regions = {} } end - table.insert(injections[tree_index][lang][pattern].nodes, injection_node) + table.insert(injections[tree_index][lang][pattern].regions, ranges) end end local result = {} -- Generate a map by lang of node lists. - -- Each list is a set of ranges that should be parsed - -- together. + -- Each list is a set of ranges that should be parsed together. for _, lang_map in ipairs(injections) do for lang, patterns in pairs(lang_map) do if not result[lang] then @@ -354,10 +382,10 @@ function LanguageTree:_get_injections() for _, entry in pairs(patterns) do if entry.combined then - table.insert(result[lang], entry.nodes) + table.insert(result[lang], vim.tbl_flatten(entry.regions)) else - for _, node in ipairs(entry.nodes) do - table.insert(result[lang], {node}) + for _, ranges in ipairs(entry.regions) do + table.insert(result[lang], ranges) end end end @@ -397,14 +425,24 @@ function LanguageTree:_on_bytes(bufnr, changed_tick, new_row, new_col, new_byte) end +function LanguageTree:_on_reload() + self:invalidate(true) +end + + +function LanguageTree:_on_detach(...) + self:invalidate(true) + self:_do_callback('detach', ...) +end + --- Registers callbacks for the parser -- @param cbs An `nvim_buf_attach`-like table argument with the following keys : -- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. --- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. +-- `on_changedtree` : a callback that will be called every time the tree has syntactical changes. -- it will only be passed one argument, that is a table of the ranges (as node ranges) that -- changed. -- `on_child_added` : emitted when a child is added to the tree. --- `on_child_removed` : emitted when a child is remvoed from the tree. +-- `on_child_removed` : emitted when a child is removed from the tree. function LanguageTree:register_cbs(cbs) if not cbs then return end @@ -416,6 +454,10 @@ function LanguageTree:register_cbs(cbs) table.insert(self._callbacks.bytes, cbs.on_bytes) end + if cbs.on_detach then + table.insert(self._callbacks.detach, cbs.on_detach) + end + if cbs.on_child_added then table.insert(self._callbacks.child_added, cbs.on_child_added) end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e49f54681d..ed5146be44 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -8,10 +8,33 @@ Query.__index = Query local M = {} +local function dedupe_files(files) + local result = {} + local seen = {} + + for _, path in ipairs(files) do + if not seen[path] then + table.insert(result, path) + seen[path] = true + end + end + + return result +end + +local function safe_read(filename, read_quantifier) + local file, err = io.open(filename, 'r') + if not file then + error(err) + end + local content = file:read(read_quantifier) + io.close(file) + return content +end function M.get_query_files(lang, query_name, is_included) local query_path = string.format('queries/%s/%s.scm', lang, query_name) - local lang_files = a.nvim_get_runtime_file(query_path, true) + local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) if #lang_files == 0 then return {} end @@ -25,7 +48,7 @@ function M.get_query_files(lang, query_name, is_included) local MODELINE_FORMAT = "^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$" for _, file in ipairs(lang_files) do - local modeline = io.open(file, 'r'):read('*l') + local modeline = safe_read(file, '*l') if modeline then local langlist = modeline:match(MODELINE_FORMAT) @@ -60,21 +83,31 @@ local function read_query_files(filenames) local contents = {} for _,filename in ipairs(filenames) do - table.insert(contents, io.open(filename, 'r'):read('*a')) + table.insert(contents, safe_read(filename, '*a')) end return table.concat(contents, '') end -local match_metatable = { - __index = function(tbl, key) - rawset(tbl, key, {}) - return tbl[key] - end -} +--- The explicitly set queries from |vim.treesitter.query.set_query()| +local explicit_queries = setmetatable({}, { + __index = function(t, k) + local lang_queries = {} + rawset(t, k, lang_queries) -local function new_match_metadata() - return setmetatable({}, match_metatable) + return lang_queries + end, +}) + +--- Sets the runtime query {query_name} for {lang} +--- +--- This allows users to override any runtime files and/or configuration +--- set by plugins. +---@param lang string: The language to use for the query +---@param query_name string: The name of the query (i.e. "highlights") +---@param text string: The query text (unparsed). +function M.set_query(lang, query_name, text) + explicit_queries[lang][query_name] = M.parse_query(lang, text) end --- Returns the runtime query {query_name} for {lang}. @@ -84,6 +117,10 @@ end -- -- @return The corresponding query, parsed. function M.get_query(lang, query_name) + if explicit_queries[lang][query_name] then + return explicit_queries[lang][query_name] + end + local query_files = M.get_query_files(lang, query_name) local query_string = read_query_files(query_files) @@ -111,7 +148,7 @@ end --- Gets the text corresponding to a given node -- @param node the node --- @param bufnr the buffer from which the node in extracted. +-- @param bufnr the buffer from which the node is extracted. function M.get_node_text(node, source) local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() @@ -211,14 +248,14 @@ predicate_handlers["vim-match?"] = predicate_handlers["match?"] -- Directives store metadata or perform side effects against a match. -- Directives should always end with a `!`. -- Directive handler receive the following arguments --- (match, pattern, bufnr, predicate) +-- (match, pattern, bufnr, predicate, metadata) local directive_handlers = { ["set!"] = function(_, _, _, pred, metadata) if #pred == 4 then - -- (set! @capture "key" "value") + -- (#set! @capture "key" "value") metadata[pred[2]][pred[3]] = pred[4] else - -- (set! "key" "value") + -- (#set! "key" "value") metadata[pred[2]] = pred[3] end end, @@ -231,7 +268,6 @@ local directive_handlers = { local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 local end_col_offset = pred[6] or 0 - local key = pred[7] or "offset" range[1] = range[1] + start_row_offset range[2] = range[2] + start_col_offset @@ -240,12 +276,12 @@ local directive_handlers = { -- If this produces an invalid range, we just skip it. if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then - metadata[pred[2]][key] = range + metadata.content = {range} end end } ---- Adds a new predicates to be used in queries +--- Adds a new predicate to be used in queries -- -- @param name the name of the predicate, without leading # -- @param handler the handler function to be used @@ -355,10 +391,10 @@ end --- Iterates of the captures of self on a given range. -- --- @param node The node under witch the search will occur +-- @param node The node under which the search will occur -- @param buffer The source buffer to search -- @param start The starting line of the search --- @param stop The stoping line of the search (end-exclusive) +-- @param stop The stopping line of the search (end-exclusive) -- -- @returns The matching capture id -- @returns The captured node @@ -372,7 +408,7 @@ function Query:iter_captures(node, source, start, stop) local raw_iter = node:_rawquery(self.query, true, start, stop) local function iter() local capture, captured_node, match = raw_iter() - local metadata = new_match_metadata() + local metadata = {} if match ~= nil then local active = self:match_preds(match, match.pattern, source) @@ -388,12 +424,12 @@ function Query:iter_captures(node, source, start, stop) return iter end ---- Iterates of the matches of self on a given range. +--- Iterates the matches of self on a given range. -- --- @param node The node under witch the search will occur +-- @param node The node under which the search will occur -- @param buffer The source buffer to search -- @param start The starting line of the search --- @param stop The stoping line of the search (end-exclusive) +-- @param stop The stopping line of the search (end-exclusive) -- -- @returns The matching pattern id -- @returns The matching match @@ -407,7 +443,7 @@ function Query:iter_matches(node, source, start, stop) local raw_iter = node:_rawquery(self.query, false, start, stop) local function iter() local pattern, match = raw_iter() - local metadata = new_match_metadata() + local metadata = {} if match ~= nil then local active = self:match_preds(match, pattern, source) diff --git a/runtime/menu.vim b/runtime/menu.vim index 3756787e7f..78306a57b8 100644 --- a/runtime/menu.vim +++ b/runtime/menu.vim @@ -2,7 +2,7 @@ " You can also use this as a start for your own set of menus. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2019 Jan 27 +" Last Change: 2019 Dec 10 " Note that ":an" (short for ":anoremenu") is often used to make a menu work " in all modes and avoid side effects from mappings defined by the user. @@ -356,8 +356,8 @@ func! s:SetupColorSchemes() abort let s:did_setup_color_schemes = 1 let n = globpath(&runtimepath, "colors/*.vim", 1, 1) - let n += globpath(&runtimepath, "pack/*/start/*/colors/*.vim", 1, 1) - let n += globpath(&runtimepath, "pack/*/opt/*/colors/*.vim", 1, 1) + let n += globpath(&packpath, "pack/*/start/*/colors/*.vim", 1, 1) + let n += globpath(&packpath, "pack/*/opt/*/colors/*.vim", 1, 1) " Ignore case for VMS and windows, sort on name let names = sort(map(n, 'substitute(v:val, "\\c.*[/\\\\:\\]]\\([^/\\\\:]*\\)\\.vim", "\\1", "")'), 1) @@ -690,11 +690,11 @@ func! s:BMShow(...) let g:bmenu_priority = a:1 endif - " remove old menu, if exists; keep one entry to avoid a torn off menu to - " disappear. - silent! unmenu &Buffers + " Remove old menu, if exists; keep one entry to avoid a torn off menu to + " disappear. Use try/catch to avoid setting v:errmsg + try | unmenu &Buffers | catch | endtry exe 'noremenu ' . g:bmenu_priority . ".1 &Buffers.Dummy l" - silent! unmenu! &Buffers + try | unmenu! &Buffers | catch | endtry " create new menu; set 'cpo' to include the <CR> let cpo_save = &cpo diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 6b3328a5d4..60dc6fdd31 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -810,6 +810,14 @@ call <SID>OptionL("ts") call append("$", "shiftwidth\tnumber of spaces used for each step of (auto)indent") call append("$", "\t(local to buffer)") call <SID>OptionL("sw") +if has("vartabs") + call append("$", "vartabstop\tlist of number of spaces a tab counts for") + call append("$", "\t(local to buffer)") + call <SID>OptionL("vts") + call append("$", "varsofttabstop\tlist of number of spaces a soft tabsstop counts for") + call append("$", "\t(local to buffer)") + call <SID>OptionL("vsts") +endif call append("$", "smarttab\ta <Tab> in an indent inserts 'shiftwidth' spaces") call <SID>BinOptionG("sta", &sta) call append("$", "softtabstop\tif non-zero, number of spaces to insert for a <Tab>") diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 6870bcec75..fa5d064048 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -43,7 +43,7 @@ " balloon -> nvim floating window " " The code for opening the floating window was taken from the beautiful -" implementation of LanguageClient-Neovim: +" implementation of LanguageClient-Neovim: " https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304 " " Neovim terminal also works seamlessly on windows, which is why the ability @@ -76,9 +76,14 @@ if !exists('g:termdebugger') endif let s:pc_id = 12 -let s:break_id = 13 " breakpoint number is added to this +let s:asm_id = 13 +let s:break_id = 14 " breakpoint number is added to this let s:stopped = 1 +let s:parsing_disasm_msg = 0 +let s:asm_lines = [] +let s:asm_addr = '' + " Take a breakpoint number as used by GDB and turn it into an integer. " The breakpoint may contain a dot: 123.4 -> 123004 " The main breakpoint has a zero subid. @@ -120,6 +125,7 @@ func s:StartDebug_internal(dict) let s:ptywin = 0 let s:pid = 0 + let s:asmwin = 0 " Uncomment this line to write logging in "debuglog". " call ch_logfile('debuglog', 'w') @@ -155,6 +161,14 @@ func s:StartDebug_internal(dict) else call s:StartDebug_term(a:dict) endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window + let curwinid = win_getid(winnr()) + call s:GotoAsmwinOrCreateIt() + call win_gotoid(curwinid) + endif + endif endfunc " Use when debugger didn't start or ended. @@ -321,9 +335,9 @@ func s:StartDebug_prompt(dict) "call ch_log('executing "' . join(cmd) . '"') let s:gdbjob = jobstart(cmd, { - \ 'on_exit': function('s:EndPromptDebug'), - \ 'on_stdout': function('s:GdbOutCallback'), - \ }) + \ 'on_exit': function('s:EndPromptDebug'), + \ 'on_stdout': function('s:GdbOutCallback'), + \ }) if s:gdbjob == 0 echoerr 'invalid argument (or job table is full) while starting gdb job' exe 'bwipe! ' . s:ptybuf @@ -562,6 +576,15 @@ func s:GetFullname(msg) return name endfunc +" Extract the "addr" value from a gdb message with addr="0x0001234". +func s:GetAsmAddr(msg) + if a:msg !~ 'addr=' + return '' + endif + let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', '')) + return addr +endfunc + function s:EndTermDebug(job_id, exit_code, event) unlet s:gdbwin @@ -601,6 +624,66 @@ func s:EndPromptDebug(job_id, exit_code, event) "call ch_log("Returning from EndPromptDebug()") endfunc +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n" +" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n" +" ... +" - CommOutput: ~" 0x0000555556467cd0:\tpop rbp\n" +" - CommOutput: ~" 0x0000555556467cd1:\tret \n" +" - CommOutput: ~"End of assembler dump.\n" +" - CommOutput: ^done + +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: &"No function contains specified address.\n" +" - CommOutput: ^error,msg="No function contains specified address." +func s:HandleDisasmMsg(msg) + if a:msg =~ '^\^done' + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + silent normal! gg0"_dG + call setline(1, s:asm_lines) + set nomodified + set filetype=asm + + let lnum = search('^' . s:asm_addr) + if lnum != 0 + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + + let s:parsing_disasm_msg = 0 + let s:asm_lines = [] + elseif a:msg =~ '^\^error,msg=' + if s:parsing_disasm_msg == 1 + " Disassemble call ran into an error. This can happen when gdb can't + " find the function frame address, so let's try to disassemble starting + " at current PC + call s:SendCommand('disassemble $pc,+100') + endif + let s:parsing_disasm_msg = 0 + elseif a:msg =~ '\&\"disassemble \$pc' + if a:msg =~ '+100' + " This is our second disasm attempt + let s:parsing_disasm_msg = 2 + endif + else + let value = substitute(a:msg, '^\~\"[ ]*', '', '') + let value = substitute(value, '^=>[ ]*', '', '') + let value = substitute(value, '\\n\"
$', '', '') + let value = substitute(value, '
', '', '') + let value = substitute(value, '\\t', ' ', 'g') + + if value != '' || !empty(s:asm_lines) + call add(s:asm_lines, value) + endif + endif +endfunc + func s:CommOutput(job_id, msgs, event) for msg in a:msgs @@ -608,7 +691,10 @@ func s:CommOutput(job_id, msgs, event) if msg[0] == "\n" let msg = msg[1:] endif - if msg != '' + + if s:parsing_disasm_msg + call s:HandleDisasmMsg(msg) + elseif msg != '' if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' call s:HandleCursor(msg) elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' @@ -621,6 +707,9 @@ func s:CommOutput(job_id, msgs, event) call s:HandleEvaluate(msg) elseif msg =~ '^\^error,msg=' call s:HandleError(msg) + elseif msg =~ '^disassemble' + let s:parsing_disasm_msg = 1 + let s:asm_lines = [] endif endif endfor @@ -651,6 +740,7 @@ func s:InstallCommands() command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) command Source call s:GotoSourcewinOrCreateIt() + command Asm call s:GotoAsmwinOrCreateIt() command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? @@ -689,6 +779,7 @@ func s:DeleteCommands() delcommand Gdb delcommand Program delcommand Source + delcommand Asm delcommand Winbar nunmap K @@ -963,6 +1054,48 @@ func s:GotoSourcewinOrCreateIt() endif endfunc +func s:GotoAsmwinOrCreateIt() + if !win_gotoid(s:asmwin) + if win_gotoid(s:sourcewin) + exe 'rightbelow new' + else + exe 'new' + endif + + let s:asmwin = win_getid(winnr()) + + setlocal nowrap + setlocal number + setlocal noswapfile + setlocal buftype=nofile + + let asmbuf = bufnr('Termdebug-asm-listing') + if asmbuf > 0 + exe 'buffer' . asmbuf + else + exe 'file Termdebug-asm-listing' + endif + + if exists('g:termdebug_disasm_window') + if g:termdebug_disasm_window > 1 + exe 'resize ' . g:termdebug_disasm_window + endif + endif + endif + + if s:asm_addr != '' + let lnum = search('^' . s:asm_addr) + if lnum == 0 + if s:stopped + call s:SendCommand('disassemble $pc') + endif + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + endif +endfunc + " Handle stopping and running message from gdb. " Will update the sign that shows the current position. func s:HandleCursor(msg) @@ -981,10 +1114,31 @@ func s:HandleCursor(msg) else let fname = '' endif + + if a:msg =~ 'addr=' + let asm_addr = s:GetAsmAddr(a:msg) + if asm_addr != '' + let s:asm_addr = asm_addr + + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + let lnum = search('^' . s:asm_addr) + if lnum == 0 + call s:SendCommand('disassemble $pc') + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif + + call win_gotoid(curwinid) + endif + endif + endif + if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') if lnum =~ '^[0-9]*$' - call s:GotoSourcewinOrCreateIt() + call s:GotoSourcewinOrCreateIt() if expand('%:p') != fnamemodify(fname, ':p') if &modified " TODO: find existing window diff --git a/runtime/plugin/netrwPlugin.vim b/runtime/plugin/netrwPlugin.vim index 2d67f6a4e4..87302cf23b 100644 --- a/runtime/plugin/netrwPlugin.vim +++ b/runtime/plugin/netrwPlugin.vim @@ -1,7 +1,7 @@ " netrwPlugin.vim: Handles file transfer and remote directory listing across a network " PLUGIN SECTION -" Date: Feb 08, 2016 -" Maintainer: Charles E Campbell <NdrOchip@ScampbellPfamily.AbizM-NOSPAM> +" Date: Feb 08, 2016 - Jan 07, 2020 +" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> " GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim " Copyright: Copyright (C) 1999-2013 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, @@ -20,7 +20,7 @@ if &cp || exists("g:loaded_netrwPlugin") finish endif -let g:loaded_netrwPlugin = "v165" +let g:loaded_netrwPlugin = "v168" let s:keepcpo = &cpo set cpo&vim "DechoRemOn @@ -81,7 +81,7 @@ if !exists("g:netrw_nogx") if !hasmapto('<Plug>NetrwBrowseX') nmap <unique> gx <Plug>NetrwBrowseX endif - nno <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(expand((exists("g:netrw_gx")? g:netrw_gx : '<cfile>')),netrw#CheckIfRemote())<cr> + nno <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))<cr> endif if maparg('gx','v') == "" if !hasmapto('<Plug>NetrwBrowseXVis') diff --git a/runtime/plugin/tarPlugin.vim b/runtime/plugin/tarPlugin.vim index 6d9e6bd540..d55492a93e 100644 --- a/runtime/plugin/tarPlugin.vim +++ b/runtime/plugin/tarPlugin.vim @@ -14,7 +14,7 @@ if &cp || exists("g:loaded_tarPlugin") finish endif -let g:loaded_tarPlugin = "v29" +let g:loaded_tarPlugin = "v32" let s:keepcpo = &cpo set cpo&vim @@ -39,11 +39,13 @@ augroup tar au BufReadCmd *.lrp call tar#Browse(expand("<amatch>")) au BufReadCmd *.tar.bz2 call tar#Browse(expand("<amatch>")) au BufReadCmd *.tar.Z call tar#Browse(expand("<amatch>")) - au BufReadCmd *.tgz call tar#Browse(expand("<amatch>")) au BufReadCmd *.tbz call tar#Browse(expand("<amatch>")) + au BufReadCmd *.tgz call tar#Browse(expand("<amatch>")) au BufReadCmd *.tar.lzma call tar#Browse(expand("<amatch>")) au BufReadCmd *.tar.xz call tar#Browse(expand("<amatch>")) au BufReadCmd *.txz call tar#Browse(expand("<amatch>")) + au BufReadCmd *.tar.zst call tar#Browse(expand("<amatch>")) + au BufReadCmd *.tzs call tar#Browse(expand("<amatch>")) augroup END com! -nargs=? -complete=file Vimuntar call tar#Vimuntar(<q-args>) diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 96b43cf0d0..260750a85b 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -14,6 +14,7 @@ "union" "volatile" "goto" + "register" ] @keyword [ @@ -81,6 +82,8 @@ "|=" "&=" "^=" + ">>=" + "<<=" "--" "++" ] @operator @@ -117,7 +120,6 @@ (preproc_arg) (preproc_defined) ] @function.macro -; TODO (preproc_arg) @embedded (field_identifier) @property (statement_identifier) @label @@ -129,13 +131,22 @@ (type_descriptor) ] @type -(declaration type: [(identifier) (type_identifier)] @type) -(cast_expression type: [(identifier) (type_identifier)] @type) +(declaration (type_qualifier) @type) +(cast_expression type: (type_descriptor) @type) (sizeof_expression value: (parenthesized_expression (identifier) @type)) ((identifier) @constant (#match? @constant "^[A-Z][A-Z0-9_]+$")) +;; Preproc def / undef +(preproc_def + name: (_) @constant) +(preproc_call + directive: (preproc_directive) @_u + argument: (_) @constant + (#eq? @_u "#undef")) + + (comment) @comment ;; Parameters diff --git a/runtime/syntax/cabal.vim b/runtime/syntax/cabal.vim index 8af47d4042..92e6b8331e 100644 --- a/runtime/syntax/cabal.vim +++ b/runtime/syntax/cabal.vim @@ -4,7 +4,7 @@ " Maintainer: Marcin Szamotulski <profunctor@pm.me> " Previous Maintainer: Vincent Berthoux <twinside@gmail.com> " File Types: .cabal -" Last Change: 15 May 2018 +" Last Change: 21 Nov 2020 " v1.5: Incorporated changes from " https://github.com/sdiehl/haskell-vim-proto/blob/master/vim/syntax/cabal.vim " Use `syn keyword` instead of `syn match`. @@ -62,11 +62,12 @@ syn keyword cabalCategory contained \ source-repository \ flag \ custom-setup + \ common syn match cabalCategoryTitle contained /[^{]*\ze{\?/ syn match cabalCategoryRegion \ contains=cabalCategory,cabalCategoryTitle \ nextgroup=cabalCategory skipwhite - \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|custom-setup\)\+\s*\%(.*$\|$\)/ + \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|custom-setup\|common\)\+\s*\%(.*$\|$\)/ syn keyword cabalTruth true false " cabalStatementRegion which limits the scope of cabalStatement keywords, this @@ -76,10 +77,14 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ default-language \ default-extensions \ author + \ autogen-modules + \ asm-sources + \ asm-options \ branch \ bug-reports \ build-depends \ build-tools + \ build-tools-depends \ build-type \ buildable \ c-sources @@ -87,32 +92,46 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ category \ cc-options \ copyright + \ cmm-sources + \ cmm-options \ cpp-options + \ cxx-sources \ data-dir \ data-files \ default + \ default-extensions \ description \ executable \ exposed-modules \ exposed \ extensions - \ extra-tmp-files + \ extra-bundled-libraries \ extra-doc-files + \ extra-dynamic-library-flavours + \ extra-framework-dirs + \ extra-ghci-libraries \ extra-lib-dirs \ extra-libraries + \ extra-library-flavours \ extra-source-files - \ exta-tmp-files + \ extra-tmp-files \ for example \ frameworks \ ghc-options \ ghc-prof-options \ ghc-shared-options + \ ghcjs-options + \ ghcjs-prof-options + \ ghcjs-shared-options \ homepage + \ hs-source-dir \ hs-source-dirs \ hugs-options + \ import \ include-dirs \ includes \ install-includes + \ js-sources \ ld-options \ license \ license-file @@ -120,10 +139,13 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ main-is \ maintainer \ manual + \ mixins \ module \ name \ nhc98-options \ other-extensions + \ other-language + \ other-languages \ other-modules \ package-url \ pkgconfig-depends diff --git a/runtime/syntax/cabalconfig.vim b/runtime/syntax/cabalconfig.vim new file mode 100644 index 0000000000..0165725c06 --- /dev/null +++ b/runtime/syntax/cabalconfig.vim @@ -0,0 +1,30 @@ +" Vim syntax file +" Language: Cabal Config +" Maintainer: profunctor@pm.me +" Last Change: Marcin Szamotulski +" Original Author: Marcin Szamotulski + +if exists("b:current_syntax") + finish +endif + +syn match CabalConfigSection /^\S[[:alpha:]]\+\%(-[[:alpha:]]\+\)*[^:]*$/ +syn region CabalConfigRegion matchgroup=CabalConfigKey start=/^\s*[[:alpha:]]\+\%(-[[:alpha:]]\+\)*:/ matchgroup=NONE end=/$/ contains=CabalConfigSeparator,CabalConfigKeyword,CabalConfigPath keepend +syn match CabalConfigComment /^\s*--.*$/ +syn match CabalConfigValue /.*$/ contained +syn match CabalConfigKey /[[:alpha:]]\+\%(-[[:alpha:]]\+\)*\ze:/ +syn keyword CabalConfigSeparator : contained +syn match CabalConfigVariable /\$[[:alpha:]]\+/ +syn keyword CabalConfigKeyword True False ghc +syn match CabalConfigPath /\%([[:alpha:]]\+:\)\?\%(\/[[:print:]]\+\)\+/ + +hi def link CabalConfigComment Comment +hi def link CabalConfigSection Title +hi def link CabalConfigKey Statement +hi def link CabalConfigSeparator NonText +hi def link CabalConfigValue Normal +hi def link CabalConfigVariable Identifier +hi def link CabalConfigKeyword Keyword +hi def link CabalConfigPath Directory + +let b:current_syntax = "cabal.config" diff --git a/runtime/syntax/cabalproject.vim b/runtime/syntax/cabalproject.vim new file mode 100644 index 0000000000..12143b9ee9 --- /dev/null +++ b/runtime/syntax/cabalproject.vim @@ -0,0 +1,28 @@ +" Vim syntax file +" Language: Cabal Project +" Maintainer: profunctor@pm.me +" Last Change: Marcin Szamotulski +" Original Author: Marcin Szamotulski + +if exists("b:current_syntax") + finish +endif + +syn match CabalProjectComment /^\s*--.*/ contains=@Spell +syn match CabalProjectField /^\w\%(\w\|-\)\+/ contains=@NoSpell + +syn keyword CabalProjectBoolean true false True False +syn keyword CabalProjectCompiler ghc ghcjs jhc lhc uhc haskell-suite +syn match CabalProjectNat /\<\d\+\>/ +syn keyword CabalProjectJobs $ncpus +syn keyword CabalProjectProfilingLevel default none exported-functions toplevel-functions all-functions + +hi def link CabalProjectComment Comment +hi def link CabalProjectField Statement +hi def link CabalProjectBoolean Boolean +hi def link CabalProjectCompiler Identifier +hi def link CabalProjectNat Number +hi def link CabalProjectJobs Number +hi def link CabalProjectProfilingLevel Statement + +let b:current_syntax = "cabal.project" diff --git a/runtime/syntax/haskell.vim b/runtime/syntax/haskell.vim index e5128a12ab..1b70b9344a 100644 --- a/runtime/syntax/haskell.vim +++ b/runtime/syntax/haskell.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Haskell " Maintainer: Haskell Cafe mailinglist <haskell-cafe@haskell.org> -" Last Change: 2018 Mar 29 by Marcin Szamotulski +" Last Change: 2020 Oct 4 by Marcin Szamotulski <profunctor@pm.me> " Original Author: John Williams <jrw@pobox.com> " " Thanks to Ryan Crumley for suggestions and John Meacham for @@ -38,8 +38,8 @@ if exists("b:current_syntax") endif " (Qualified) identifiers (no default highlighting) -syn match ConId "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=\<[A-Z][a-zA-Z0-9_']*\>" contains=@NoSpell -syn match VarId "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=\<[a-z][a-zA-Z0-9_']*\>" contains=@NoSpell +syn match ConId "\(\<[A-Z][a-zA-Z0-9_']*\.\)*\<[A-Z][a-zA-Z0-9_']*\>" contains=@NoSpell +syn match VarId "\(\<[A-Z][a-zA-Z0-9_']*\.\)*\<[a-z][a-zA-Z0-9_']*\>" contains=@NoSpell " Infix operators--most punctuation characters and any (qualified) identifier " enclosed in `backquotes`. An operator starting with : is a constructor, @@ -49,8 +49,11 @@ syn match hsConSym "\(\<[A-Z][a-zA-Z0-9_']*\.\)\=:[-!#$%&\*\+./<=>\?@\\^|~:]*" syn match hsVarSym "`\(\<[A-Z][a-zA-Z0-9_']*\.\)\=[a-z][a-zA-Z0-9_']*`" syn match hsConSym "`\(\<[A-Z][a-zA-Z0-9_']*\.\)\=[A-Z][a-zA-Z0-9_']*`" +" (Non-qualified) identifiers which start with # are labels +syn match hsLabel "#[a-z][a-zA-Z0-9_']*\>" + " Reserved symbols--cannot be overloaded. -syn match hsDelimiter "(\|)\|\[\|\]\|,\|;\|_\|{\|}" +syn match hsDelimiter "(\|)\|\[\|\]\|,\|;\|{\|}" " Strings and constants syn match hsSpecialChar contained "\\\([0-9]\+\|o[0-7]\+\|x[0-9a-fA-F]\+\|[\"\\'&\\abfnrtv]\|^[A-Z^_\[\\\]]\)" @@ -62,37 +65,41 @@ syn match hsCharacter "^'\([^\\]\|\\[^']\+\|\\'\)'" contains=hsSpecialChar,hs syn match hsNumber "\v<[0-9]%(_*[0-9])*>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*>|<0[oO]_*%(_*[0-7])*>|<0[bB]_*[01]%(_*[01])*>" syn match hsFloat "\v<[0-9]%(_*[0-9])*\.[0-9]%(_*[0-9])*%(_*[eE][-+]?[0-9]%(_*[0-9])*)?>|<[0-9]%(_*[0-9])*_*[eE][-+]?[0-9]%(_*[0-9])*>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*\.[0-9a-fA-F]%(_*[0-9a-fA-F])*%(_*[pP][-+]?[0-9]%(_*[0-9])*)?>|<0[xX]_*[0-9a-fA-F]%(_*[0-9a-fA-F])*_*[pP][-+]?[0-9]%(_*[0-9])*>" -" Keyword definitions. These must be patterns instead of keywords -" because otherwise they would match as keywords at the start of a -" "literate" comment (see lhs.vim). -syn match hsModule "\<module\>" -syn match hsImport "\<import\>.*"he=s+6 contains=hsImportMod,hsLineComment,hsBlockComment,@NoSpell -syn match hsImportMod contained "\<\(as\|qualified\|hiding\)\>" contains=@NoSpell -syn match hsInfix "\<\(infix\|infixl\|infixr\)\>" -syn match hsStructure "\<\(class\|data\|deriving\|instance\|default\|where\)\>" -syn match hsTypedef "\<\(type\|newtype\)\>" -syn match hsStatement "\<\(do\|case\|of\|let\|in\)\>" -syn match hsConditional "\<\(if\|then\|else\)\>" +" Keyword definitions. +syn keyword hsModule module +syn match hsImportGroup "\<import\>.*" contains=hsImport,hsImportModuleName,hsImportMod,hsLineComment,hsBlockComment,hsImportList,@NoSpell nextgroup=hsImport +syn keyword hsImport import contained nextgroup=hsImportModuleName +syn match hsImportModuleName '\<[A-Z][A-Za-z.]*' contained +syn region hsImportList start='(' skip='([^)]\{-})' end=')' keepend contained contains=ConId,VarId,hsDelimiter,hsBlockComment,hsTypedef,@NoSpell + +syn keyword hsImportMod contained as qualified hiding +syn keyword hsInfix infix infixl infixr +syn keyword hsStructure class data deriving instance default where +syn keyword hsTypedef type +syn keyword hsNewtypedef newtype +syn keyword hsTypeFam family +syn keyword hsStatement mdo do case of let in +syn keyword hsConditional if then else " Not real keywords, but close. if exists("hs_highlight_boolean") " Boolean constants from the standard prelude. - syn match hsBoolean "\<\(True\|False\)\>" + syn keyword hsBoolean True False endif if exists("hs_highlight_types") " Primitive types from the standard prelude and libraries. - syn match hsType "\<\(Int\|Integer\|Char\|Bool\|Float\|Double\|IO\|Void\|Addr\|Array\|String\)\>" + syn keyword hsType Int Integer Char Bool Float Double IO Void Addr Array String endif if exists("hs_highlight_more_types") " Types from the standard prelude libraries. - syn match hsType "\<\(Maybe\|Either\|Ratio\|Complex\|Ordering\|IOError\|IOResult\|ExitCode\)\>" - syn match hsMaybe "\<Nothing\>" - syn match hsExitCode "\<\(ExitSuccess\)\>" - syn match hsOrdering "\<\(GT\|LT\|EQ\)\>" + syn keyword hsType Maybe Either Ratio Complex Ordering IOError IOResult ExitCode + syn keyword hsMaybe Nothing + syn keyword hsExitCode ExitSuccess + syn keyword hsOrdering GT LT EQ endif if exists("hs_highlight_debug") " Debugging functions from the standard prelude. - syn match hsDebug "\<\(undefined\|error\|trace\)\>" + syn keyword hsDebug undefined error trace endif @@ -133,12 +140,14 @@ hi def link hsImportMod hsImport hi def link hsInfix PreProc hi def link hsStructure Structure hi def link hsStatement Statement -hi def link hsConditional Conditional -hi def link hsSpecialChar SpecialChar +hi def link hsConditional Conditional +hi def link hsSpecialChar SpecialChar hi def link hsTypedef Typedef +hi def link hsNewtypedef Typedef hi def link hsVarSym hsOperator hi def link hsConSym hsOperator hi def link hsOperator Operator +hi def link hsTypeFam Structure if exists("hs_highlight_delimiters") " Some people find this highlighting distracting. hi def link hsDelimiter Delimiter @@ -160,22 +169,22 @@ hi def link hsMaybe hsEnumConst hi def link hsOrdering hsEnumConst hi def link hsEnumConst Constant hi def link hsDebug Debug - -hi def link cCppString hsString -hi def link cCommentStart hsComment -hi def link cCommentError hsError -hi def link cCommentStartError hsError -hi def link cInclude Include -hi def link cPreProc PreProc -hi def link cDefine Macro -hi def link cIncluded hsString -hi def link cError Error -hi def link cPreCondit PreCondit -hi def link cComment Comment -hi def link cCppSkip cCppOut -hi def link cCppOut2 cCppOut -hi def link cCppOut Comment - +hi def link hsLabel Special + +hi def link cCppString hsString +hi def link cCommentStart hsComment +hi def link cCommentError hsError +hi def link cCommentStartError hsError +hi def link cInclude Include +hi def link cPreProc PreProc +hi def link cDefine Macro +hi def link cIncluded hsString +hi def link cError Error +hi def link cPreCondit PreCondit +hi def link cComment Comment +hi def link cCppSkip cCppOut +hi def link cCppOut2 cCppOut +hi def link cCppOut Comment let b:current_syntax = "haskell" diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim new file mode 100644 index 0000000000..d5c1414f01 --- /dev/null +++ b/runtime/syntax/lsp_markdown.vim @@ -0,0 +1,15 @@ +" Vim syntax file +" Language: lsp_markdown +" Maintainer: Michael Lingelbach <m.j.lbach@gmail.com +" URL: http://neovim.io +" Remark: Uses markdown syntax file + +runtime! syntax/markdown.vim + +syn cluster mkdNonListItem add=mkdEscape,mkdNbsp + +syntax region mkdEscape matchgroup=mkdEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/.\zs/ keepend contains=mkdEscapeCh oneline concealends +syntax match mkdEscapeCh /./ contained +syntax match mkdNbsp / / conceal cchar= + +hi def link mkdEscape special diff --git a/runtime/syntax/netrw.vim b/runtime/syntax/netrw.vim index c4d3cf5fda..83ae17445d 100644 --- a/runtime/syntax/netrw.vim +++ b/runtime/syntax/netrw.vim @@ -1,7 +1,7 @@ " Language : Netrw Listing Syntax " Maintainer : Charles E. Campbell -" Last change: Oct 31, 2016 -" Version : 20 NOT RELEASED +" Last change: Nov 07, 2019 +" Version : 20 " --------------------------------------------------------------------- if exists("b:current_syntax") finish diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 1a37af1c8a..92348d57ec 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -616,7 +616,7 @@ syn region vimGlobal matchgroup=Statement start='\<v\%[global]!\=/' skip='\\.' e " g:vimsyn_embed =~# 'r' : embed ruby " g:vimsyn_embed =~# 't' : embed tcl if !exists("g:vimsyn_embed") - let g:vimsyn_embed= 0 + let g:vimsyn_embed = 'l' endif " [-- lua --] {{{3 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/gen_vimdoc.py b/scripts/gen_vimdoc.py index 0507e4b7b6..b4d896fecc 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -48,6 +48,7 @@ import textwrap import subprocess import collections import msgpack +import logging from xml.dom import minidom @@ -57,10 +58,18 @@ if sys.version_info < MIN_PYTHON_VERSION: print("requires Python {}.{}+".format(*MIN_PYTHON_VERSION)) sys.exit(1) -DEBUG = ('DEBUG' in os.environ) +# DEBUG = ('DEBUG' in os.environ) INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ) INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ) +log = logging.getLogger(__name__) + +LOG_LEVELS = { + logging.getLevelName(level): level for level in [ + logging.DEBUG, logging.INFO, logging.ERROR + ] +} + fmt_vimhelp = False # HACK text_width = 78 script_path = os.path.abspath(__file__) @@ -157,7 +166,7 @@ CONFIG = { ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', - 'section_name': {}, + 'section_name': {'lsp.lua': 'lsp'}, 'section_fmt': lambda name: ( 'Lua module: vim.lsp' if name.lower() == 'lsp' @@ -726,8 +735,8 @@ def extract_from_xml(filename, target, width): if desc: for child in desc.childNodes: paras.append(para_as_map(child)) - if DEBUG: - print(textwrap.indent( + log.debug( + textwrap.indent( re.sub(r'\n\s*\n+', '\n', desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16)) @@ -885,12 +894,13 @@ def main(config, args): os.remove(mpack_file) output_dir = out_dir.format(target=target) + debug = args.log_level >= logging.DEBUG p = subprocess.Popen( ['doxygen', '-'], stdin=subprocess.PIPE, # silence warnings # runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found - stderr=(subprocess.STDOUT if DEBUG else subprocess.DEVNULL)) + stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL)) p.communicate( config.format( input=CONFIG[target]['files'], @@ -1039,6 +1049,10 @@ def filter_source(filename): def parse_args(): targets = ', '.join(CONFIG.keys()) ap = argparse.ArgumentParser() + ap.add_argument( + "--log-level", "-l", choices=LOG_LEVELS.keys(), + default=logging.getLevelName(logging.ERROR), help="Set log verbosity" + ) ap.add_argument('source_filter', nargs='*', help="Filter source file(s)") ap.add_argument('-k', '--keep-tmpfiles', action='store_true', @@ -1085,6 +1099,10 @@ Doxyfile = textwrap.dedent(''' if __name__ == "__main__": args = parse_args() + print("Setting log level to %s" % args.log_level) + args.log_level = LOG_LEVELS[args.log_level] + log.setLevel(args.log_level) + if len(args.source_filter) > 0: filter_source(args.source_filter[0]) else: diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter index 61577527c4..8760f12176 100755 --- a/scripts/lua2dox_filter +++ b/scripts/lua2dox_filter @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ########################################################################### # Copyright (C) 2012 by Simon Dales # 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/CMakeLists.txt b/src/nvim/CMakeLists.txt index db77931c16..2c9d655a15 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -284,7 +284,7 @@ foreach(sfile ${NVIM_SOURCES} endif() add_custom_command( OUTPUT "${gf_c_h}" "${gf_h_h}" - COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY} + COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}" DEPENDS ${depends}) list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}") @@ -513,8 +513,44 @@ if(WIN32) tidy.exe win32yank.exe winpty-agent.exe + winpty.dll xxd.exe + # Dependencies for neovim-qt + bearer/qgenericbearer.dll + iconengines/qsvgicon.dll + imageformats/qgif.dll + imageformats/qicns.dll + imageformats/qico.dll + imageformats/qjpeg.dll + imageformats/qsvg.dll + imageformats/qtga.dll + imageformats/qtiff.dll + imageformats/qwbmp.dll + imageformats/qwebp.dll + platforms/qwindows.dll + styles/qwindowsvistastyle.dll + translations/qt_ar.qm + translations/qt_bg.qm + translations/qt_ca.qm + translations/qt_cs.qm + translations/qt_da.qm + translations/qt_de.qm + translations/qt_en.qm + translations/qt_es.qm + translations/qt_fi.qm + translations/qt_fr.qm + translations/qt_gd.qm + translations/qt_he.qm + translations/qt_hu.qm + translations/qt_it.qm + translations/qt_ja.qm + translations/qt_ko.qm + translations/qt_lv.qm + translations/qt_pl.qm + translations/qt_ru.qm + translations/qt_sk.qm + translations/qt_uk.qm D3Dcompiler_47.dll libEGL.dll libgcc_s_dw2-1.dll @@ -522,14 +558,13 @@ if(WIN32) libstdc++-6.dll libwinpthread-1.dll nvim-qt.exe + opengl32sw.dll Qt5Core.dll Qt5Gui.dll Qt5Network.dll Qt5Svg.dll Qt5Widgets.dll - winpty.dll - platforms/qwindows.dll ) get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY) set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n" diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2890d89bc0..e79a7a2de2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -111,6 +111,24 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - byte count of previous contents /// - deleted_codepoints (if `utf_sizes` is true) /// - deleted_codeunits (if `utf_sizes` is true) +/// - on_bytes: lua callback invoked on change. +/// This callback receives more granular information about the +/// change compared to on_lines. +/// Return `true` to detach. +/// Args: +/// - the string "bytes" +/// - buffer handle +/// - b:changedtick +/// - start row of the changed text (zero-indexed) +/// - start column of the changed text +/// - byte offset of the changed text (from the start of +/// the buffer) +/// - old end row of the changed text +/// - old end column of the changed text +/// - old end byte length of the changed text +/// - new end row of the changed text +/// - new end column of the changed text +/// - new end byte length of the changed text /// - on_changedtick: Lua callback invoked on changedtick /// increment without text change. Args: /// - the string "changedtick" @@ -119,6 +137,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - on_detach: Lua callback invoked on detach. Args: /// - the string "detach" /// - buffer handle +/// - on_reload: Lua callback invoked on reload. The entire buffer +/// content should be considered changed. Args: +/// - the string "detach" +/// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. /// - preview: also attach to command preview (i.e. 'inccommand') @@ -141,50 +163,57 @@ Boolean nvim_buf_attach(uint64_t channel_id, bool is_lua = (channel_id == LUA_INTERNAL_CALL); BufUpdateCallbacks cb = BUF_UPDATE_CALLBACKS_INIT; + struct { + const char *name; + LuaRef *dest; + } cbs[] = { + { "on_lines", &cb.on_lines }, + { "on_bytes", &cb.on_bytes }, + { "on_changedtick", &cb.on_changedtick }, + { "on_detach", &cb.on_detach }, + { "on_reload", &cb.on_reload }, + { NULL, NULL }, + }; + for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (is_lua && strequal("on_lines", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_lines = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_bytes", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_bytes = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_changedtick", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_changedtick = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("on_detach", k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, "callback is not a function"); - goto error; - } - cb.on_detach = v->data.luaref; - v->data.luaref = LUA_NOREF; - } else if (is_lua && strequal("utf_sizes", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); - goto error; + bool key_used = false; + if (is_lua) { + for (size_t j = 0; cbs[j].name; j++) { + if (strequal(cbs[j].name, k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[j].name); + goto error; + } + *(cbs[j].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; + key_used = true; + break; + } } - cb.utf_sizes = v->data.boolean; - } else if (is_lua && strequal("preview", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "preview must be boolean"); - goto error; + + if (key_used) { + continue; + } else if (strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; + key_used = true; + } else if (strequal("preview", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "preview must be boolean"); + goto error; + } + cb.preview = v->data.boolean; + key_used = true; } - cb.preview = v->data.boolean; - } else { + } + + if (!key_used) { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; } @@ -193,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, return buf_updates_register(buf, channel_id, cb, send_buffer); error: - // TODO(bfredl): ASAN build should check that the ref table is empty? - api_free_luaref(cb.on_lines); - api_free_luaref(cb.on_bytes); - api_free_luaref(cb.on_changedtick); - api_free_luaref(cb.on_detach); + buffer_update_callbacks_free(cb); return false; } @@ -722,7 +747,8 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, kExtmarkUndo); - changed_lines((linenr_T)start_row, 0, (linenr_T)end_row, (long)extra, true); + changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, + (long)extra, true); // adjust cursor like an extmark ( i e it was inside last_part_len) if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) { @@ -1200,8 +1226,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, @@ -1396,6 +1421,27 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// - hl_group : name of the highlight group used to highlight /// this mark. /// - virt_text : virtual text to link to this mark. +/// - virt_text_pos : positioning of virtual text. Possible +/// values: +/// - "eol": right after eol character (default) +/// - "overlay": display over the specified column, without +/// shifting the underlying text. +/// - virt_text_hide : hide the virtual text when the background +/// text is selected or hidden due to +/// horizontal scroll 'nowrap' +/// - hl_mode : control how highlights are combined with the +/// highlights of the text. Currently only affects +/// virt_text highlights, but might affect `hl_group` +/// in later versions. +/// - "replace": only show the virt_text color. This is the +/// default +/// - "combine": combine with background text color +/// - "blend": blend with background text color. +/// - hl_eol : when true, for a multiline highlight covering the +/// EOL of a line, continue the highlight for the rest +/// of the screen line (just like for diff and +/// cursorline highlight). +/// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -1407,6 +1453,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// 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. +/// - priority: a priority value for the highlight group. For +/// example treesitter highlighting uses a value of 100. /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, @@ -1443,10 +1491,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, bool ephemeral = false; uint64_t id = 0; - int line2 = -1, hl_id = 0; - DecorPriority priority = DECOR_PRIORITY_BASE; - colnr_T col2 = 0; - VirtText virt_text = KV_INITIAL_VALUE; + int line2 = -1; + Decoration decor = DECORATION_INIT; + colnr_T col2 = -1; + bool right_gravity = true; bool end_right_gravity = false; bool end_gravity_set = false; @@ -1493,12 +1541,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, switch (v->type) { case kObjectTypeString: hl_group = v->data.string; - hl_id = syn_check_group( + decor.hl_id = syn_check_group( (char_u *)(hl_group.data), (int)hl_group.size); break; case kObjectTypeInteger: - hl_id = (int)v->data.integer; + decor.hl_id = (int)v->data.integer; break; default: api_set_error(err, kErrorTypeValidation, @@ -1511,10 +1559,55 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "virt_text is not an Array"); goto error; } - virt_text = parse_virt_text(v->data.array, err); + decor.virt_text = parse_virt_text(v->data.array, err); if (ERROR_SET(err)) { goto error; } + } else if (strequal("virt_text_pos", k.data)) { + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos is not a String"); + goto error; + } + String str = v->data.string; + if (strequal("eol", str.data)) { + decor.virt_text_pos = kVTEndOfLine; + } else if (strequal("overlay", str.data)) { + decor.virt_text_pos = kVTOverlay; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } + } else if (strequal("virt_text_hide", k.data)) { + decor.virt_text_hide = api_object_to_bool(*v, + "virt_text_hide", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("hl_eol", k.data)) { + decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err); + if (ERROR_SET(err)) { + goto error; + } + } else if (strequal("hl_mode", k.data)) { + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "hl_mode is not a String"); + goto error; + } + String str = v->data.string; + if (strequal("replace", str.data)) { + decor.hl_mode = kHlModeReplace; + } else if (strequal("combine", str.data)) { + decor.hl_mode = kHlModeCombine; + } else if (strequal("blend", str.data)) { + decor.hl_mode = kHlModeBlend; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } } else if (strequal("ephemeral", k.data)) { ephemeral = api_object_to_bool(*v, "ephemeral", false, err); if (ERROR_SET(err)) { @@ -1532,7 +1625,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, "priority is not a valid value"); goto error; } - priority = (DecorPriority)v->data.integer; + decor.priority = (DecorPriority)v->data.integer; } else if (strequal("right_gravity", k.data)) { if (v->type != kObjectTypeBoolean) { api_set_error(err, kErrorTypeValidation, @@ -1556,7 +1649,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, // 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) { + if (line2 == -1 && col2 == -1 && end_gravity_set) { api_set_error(err, kErrorTypeValidation, "cannot set end_right_gravity " "without setting end_line or end_col"); @@ -1580,40 +1673,39 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, col2 = 0; } + Decoration *d = NULL; + + if (ephemeral) { + d = &decor; + } else if (kv_size(decor.virt_text) + || decor.priority != DECOR_PRIORITY_BASE + || decor.hl_eol) { + // TODO(bfredl): this is a bit sketchy. eventually we should + // have predefined decorations for both marks/ephemerals + d = xcalloc(1, sizeof(*d)); + *d = decor; + } else if (decor.hl_id) { + d = decor_hl(decor.hl_id); + } + // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; - VirtText *vt_allocated = NULL; - if (kv_size(virt_text)) { - vt_allocated = xmalloc(sizeof *vt_allocated); - *vt_allocated = virt_text; - } - decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, priority, vt_allocated); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); goto error; } - Decoration *decor = NULL; - if (kv_size(virt_text)) { - decor = xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->virt_text = virt_text; - } else if (hl_id) { - decor = decor_hl(hl_id); - decor->priority = priority; - } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, - line2, col2, decor, right_gravity, + line2, col2, d, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; error: - clear_virttext(&virt_text); + clear_virttext(&decor.virt_text); return 0; } @@ -1674,7 +1766,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// @param[out] err Error details, if any /// @return The ns_id that was used Integer nvim_buf_add_highlight(Buffer buffer, - Integer src_id, + Integer ns_id, String hl_group, Integer line, Integer col_start, @@ -1699,18 +1791,18 @@ Integer nvim_buf_add_highlight(Buffer buffer, col_end = MAXCOL; } - uint64_t ns_id = src2ns(&src_id); + uint64_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range - return src_id; + return ns_id; } int hl_id = 0; if (hl_group.size > 0) { hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); } else { - return src_id; + return ns_id; } int end_line = (int)line; @@ -1719,11 +1811,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, end_line++; } - extmark_set(buf, ns_id, 0, + extmark_set(buf, ns, 0, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, decor_hl(hl_id), true, false, kExtmarkNoUndo); - return src_id; + return ns_id; } /// Clears namespaced objects (highlights, extmarks, virtual text) from @@ -1919,7 +2011,6 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) curwin->w_cursor.lnum += extra; check_cursor_col(); } else if (extra < 0) { - curwin->w_cursor.lnum = lo; check_cursor(); } else { check_cursor_col(); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7cee569989..24ba6110c4 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what, } } +int object_to_hl_id(Object obj, const char *what, Error *err) +{ + if (obj.type == kObjectTypeString) { + String str = obj.data.string; + return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; + } else if (obj.type == kObjectTypeInteger) { + return (int)obj.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "%s is not a valid highlight", what); + return 0; + } +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -1694,29 +1708,362 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -DecorProvider *get_provider(NS ns_id, bool force) +static bool parse_float_anchor(String anchor, FloatAnchor *out) { - ssize_t i; - for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { - DecorProvider *item = &kv_A(decor_providers, i); - if (item->ns_id == ns_id) { - return item; - } else if (item->ns_id > ns_id) { - break; + if (anchor.size == 0) { + *out = (FloatAnchor)0; + } + char *str = anchor.data; + if (striequal(str, "NW")) { + *out = 0; // NW is the default + } else if (striequal(str, "NE")) { + *out = kFloatAnchorEast; + } else if (striequal(str, "SW")) { + *out = kFloatAnchorSouth; + } else if (striequal(str, "SE")) { + *out = kFloatAnchorSouth | kFloatAnchorEast; + } else { + return false; + } + return true; +} + +static bool parse_float_relative(String relative, FloatRelative *out) +{ + char *str = relative.data; + if (striequal(str, "editor")) { + *out = kFloatRelativeEditor; + } else if (striequal(str, "win")) { + *out = kFloatRelativeWindow; + } else if (striequal(str, "cursor")) { + *out = kFloatRelativeCursor; + } else { + return false; + } + return true; +} + +static bool parse_float_bufpos(Array bufpos, lpos_T *out) +{ + if (bufpos.size != 2 + || bufpos.items[0].type != kObjectTypeInteger + || bufpos.items[1].type != kObjectTypeInteger) { + return false; + } + out->lnum = bufpos.items[0].data.integer; + out->col = (colnr_T)bufpos.items[1].data.integer; + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +{ + struct { + const char *name; + schar_T chars[8]; + bool shadow_color; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, + { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, + { NULL, { { NUL } } , false }, + }; + + schar_T *chars = fconfig->border_chars; + int *hl_ids = fconfig->border_hl_ids; + + fconfig->border = true; + + if (style.type == kObjectTypeArray) { + Array arr = style.data.array; + size_t size = arr.size; + if (!size || size > 8 || (size & (size-1))) { + api_set_error(err, kErrorTypeValidation, + "invalid number of border chars"); + return; } + for (size_t i = 0; i < size; i++) { + Object iytem = arr.items[i]; + String string = NULL_STRING; + int hl_id = 0; + if (iytem.type == kObjectTypeArray) { + Array iarr = iytem.data.array; + if (!iarr.size || iarr.size > 2) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (iarr.items[0].type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + string = iarr.items[0].data.string; + if (iarr.size == 2) { + hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); + if (ERROR_SET(err)) { + return; + } + } + + } else if (iytem.type == kObjectTypeString) { + string = iytem.data.string; + } else { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (string.size + && mb_string2cells_len((char_u *)string.data, string.size) > 1) { + api_set_error(err, kErrorTypeValidation, + "border chars must be one cell"); + return; + } + size_t len = MIN(string.size, sizeof(*chars)-1); + if (len) { + memcpy(chars[i], string.data, len); + } + chars[i][len] = NUL; + hl_ids[i] = hl_id; + } + while (size < 8) { + memcpy(chars+size, chars, sizeof(*chars) * size); + memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); + size <<= 1; + } + if ((chars[7][0] && chars[1][0] && !chars[0][0]) + || (chars[1][0] && chars[3][0] && !chars[2][0]) + || (chars[3][0] && chars[5][0] && !chars[4][0]) + || (chars[5][0] && chars[7][0] && !chars[6][0])) { + api_set_error(err, kErrorTypeValidation, + "corner between used edges must be specified"); + } + } else if (style.type == kObjectTypeString) { + String str = style.data.string; + if (str.size == 0 || strequal(str.data, "none")) { + fconfig->border = false; + return; + } + for (size_t i = 0; defaults[i].name; i++) { + if (strequal(str.data, defaults[i].name)) { + memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); + memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + if (defaults[i].shadow_color) { + int hl_blend = SYN_GROUP_STATIC("FloatShadow"); + int hl_through = SYN_GROUP_STATIC("FloatShadowThrough"); + hl_ids[2] = hl_through; + hl_ids[3] = hl_blend; + hl_ids[4] = hl_blend; + hl_ids[5] = hl_blend; + hl_ids[6] = hl_through; + } + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); } +} - if (!force) { - return NULL; +bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, + Error *err) +{ + // TODO(bfredl): use a get/has_key interface instead and get rid of extra + // flags + bool has_row = false, has_col = false, has_relative = false; + bool has_external = false, has_window = false; + bool has_width = false, has_height = false; + bool has_bufpos = false; + + for (size_t i = 0; i < config.size; i++) { + char *key = config.items[i].key.data; + Object val = config.items[i].value; + if (!strcmp(key, "row")) { + has_row = true; + if (val.type == kObjectTypeInteger) { + fconfig->row = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->row = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'row' key must be Integer or Float"); + return false; + } + } else if (!strcmp(key, "col")) { + has_col = true; + if (val.type == kObjectTypeInteger) { + fconfig->col = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->col = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'col' key must be Integer or Float"); + return false; + } + } else if (strequal(key, "width")) { + has_width = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->width = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'width' key must be a positive Integer"); + return false; + } + } else if (strequal(key, "height")) { + has_height = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->height= (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'height' key must be a positive Integer"); + return false; + } + } else if (!strcmp(key, "anchor")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'anchor' key must be String"); + return false; + } + if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'anchor' key"); + return false; + } + } else if (!strcmp(key, "relative")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'relative' key must be String"); + return false; + } + // ignore empty string, to match nvim_win_get_config + if (val.data.string.size > 0) { + has_relative = true; + if (!parse_float_relative(val.data.string, &fconfig->relative)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'relative' key"); + return false; + } + } + } else if (!strcmp(key, "win")) { + has_window = true; + if (val.type != kObjectTypeInteger + && val.type != kObjectTypeWindow) { + api_set_error(err, kErrorTypeValidation, + "'win' key must be Integer or Window"); + return false; + } + fconfig->window = (Window)val.data.integer; + } else if (!strcmp(key, "bufpos")) { + if (val.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, + "'bufpos' key must be Array"); + return false; + } + if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'bufpos' key"); + return false; + } + has_bufpos = true; + } else if (!strcmp(key, "external")) { + if (val.type == kObjectTypeInteger) { + fconfig->external = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->external = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'external' key must be Boolean"); + return false; + } + has_external = fconfig->external; + } else if (!strcmp(key, "focusable")) { + if (val.type == kObjectTypeInteger) { + fconfig->focusable = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->focusable = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'focusable' key must be Boolean"); + return false; + } + } else if (!strcmp(key, "border")) { + parse_border_style(val, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } else if (!strcmp(key, "style")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'style' key must be String"); + return false; + } + if (val.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(val.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'style' key"); + } + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid key '%s'", key); + return false; + } + } + + if (has_window && !(has_relative + && fconfig->relative == kFloatRelativeWindow)) { + api_set_error(err, kErrorTypeValidation, + "'win' key is only valid with relative='win'"); + return false; + } + + if ((has_relative && fconfig->relative == kFloatRelativeWindow) + && (!has_window || fconfig->window == 0)) { + fconfig->window = curwin->handle; } - for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { - // allocates if needed: - (void)kv_a(decor_providers, (size_t)j+1); - kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + if (has_window && !has_bufpos) { + fconfig->bufpos.lnum = -1; } - DecorProvider *item = &kv_a(decor_providers, (size_t)i); - *item = DECORATION_PROVIDER_INIT(ns_id); - return item; + if (has_bufpos) { + if (!has_row) { + fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; + has_row = true; + } + if (!has_col) { + fconfig->col = 0; + has_col = true; + } + } + + if (has_relative && has_external) { + api_set_error(err, kErrorTypeValidation, + "Only one of 'relative' and 'external' must be used"); + return false; + } else if (!reconf && !has_relative && !has_external) { + api_set_error(err, kErrorTypeValidation, + "One of 'relative' and 'external' must be used"); + return false; + } else if (has_relative) { + fconfig->external = false; + } + + if (!reconf && !(has_height && has_width)) { + api_set_error(err, kErrorTypeValidation, + "Must specify 'width' and 'height'"); + return false; + } + + if (fconfig->external && !ui_has(kUIMultigrid)) { + api_set_error(err, kErrorTypeValidation, + "UI doesn't support external windows"); + return false; + } + + if (has_relative != has_row || has_row != has_col) { + api_set_error(err, kErrorTypeValidation, + "'relative' requires 'row'/'col' or 'bufpos'"); + return false; + } + return true; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 09895a2119..b5e53beabe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/fileio.h" +#include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" @@ -241,8 +242,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) /// /// @param ns_id the namespace to activate /// @param[out] err Error details, if any -void nvim_set_hl_ns(Integer ns_id, Error *err) - FUNC_API_SINCE(7) +void nvim__set_hl_ns(Integer ns_id, Error *err) FUNC_API_FAST { if (ns_id >= 0) { @@ -545,6 +545,26 @@ Object nvim_exec_lua(String code, Array args, Error *err) return nlua_exec(code, args, err); } +/// Notify the user with a message +/// +/// Relays the call to vim.notify . By default forwards your message in the +/// echo area but can be overriden to trigger desktop notifications. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = STRING_OBJ(msg); + args.items[1] = INTEGER_OBJ(log_level); + args.items[2] = DICTIONARY_OBJ(opts); + + return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err); +} + /// Calls a VimL function. /// /// @param fn Function name @@ -1226,6 +1246,100 @@ fail: return 0; } +/// Open a terminal instance in a buffer +/// +/// By default (and currently the only option) the terminal will not be +/// connected to an external process. Instead, input send on the channel +/// will be echoed directly by the terminal. This is useful to disply +/// ANSI terminal sequences returned as part of a rpc message, or similar. +/// +/// Note: to directly initiate the terminal using the right size, display the +/// buffer in a configured window before calling this. For instance, for a +/// floating display, first create an empty buffer using |nvim_create_buf()|, +/// then display it using |nvim_open_win()|, and then call this function. +/// Then |nvim_chan_send()| cal be called immediately to process sequences +/// in a virtual terminal having the intended size. +/// +/// @param buffer the buffer to use (expected to be empty) +/// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any +Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + TerminalOptions topts; + Channel *chan = channel_alloc(kChannelStreamInternal); + topts.data = chan; + // NB: overriden in terminal_check_size if a window is already + // displaying the buffer + topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0); + topts.height = (uint16_t)curwin->w_height_inner; + topts.write_cb = term_write; + topts.resize_cb = term_resize; + topts.close_cb = term_close; + Terminal *term = terminal_open(buf, topts); + terminal_check_size(term); + chan->term = term; + channel_incref(chan); + return (Integer)chan->id; +} + +static void term_write(char *buf, size_t size, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_resize(uint16_t width, uint16_t height, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_close(void *data) +{ + Channel *chan = data; + terminal_destroy(chan->term); + chan->term = NULL; + channel_decref(chan); +} + + +/// Send data to channel `id`. For a job, it writes it to the +/// stdin of the process. For the stdio channel |channel-stdio|, +/// it writes to Nvim's stdout. For an internal terminal instance +/// (|nvim_open_term()|) it writes directly to terimal output. +/// See |channel-bytes| for more information. +/// +/// This function writes raw data, not RPC messages. If the channel +/// was created with `rpc=true` then the channel expects RPC +/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead. +/// +/// @param chan id of the channel +/// @param data data to write. 8-bit clean: can contain NUL bytes. +/// @param[out] err Error details, if any +void nvim_chan_send(Integer chan, String data, Error *err) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY +{ + const char *error = NULL; + if (!data.size) { + return; + } + + channel_send((uint64_t)chan, data.data, data.size, + false, &error); + if (error) { + api_set_error(err, kErrorTypeValidation, "%s", error); + } +} + /// Open a new window. /// /// Currently this is used to open floating and external windows. @@ -1303,6 +1417,29 @@ fail: /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. +/// - `border`: style of (optional) window border. This can either be a string +/// or an array. the string values are: +/// - "none" No border. This is the default +/// - "single" a single line box +/// - "double" a double line box +/// - "shadow" a drop shadow effect by blending with the background. +/// If it is an array it should be an array of eight items or any divisor of +/// eight. The array will specifify the eight chars building up the border +/// in a clockwise fashion starting with the top-left corner. As, an +/// example, the double box style could be specified as: +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] +/// if the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as: +/// [ "/", "-", "\\", "|" ] +/// or all chars the same as: +/// [ "x" ] +/// An empty string can be used to turn off a specific border, for instance: +/// [ "", "", "", ">", "", "", "", "<" ] +/// will only make vertical borders but not horizontal ones. +/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// when not defined. It could also be specified by character: +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -1526,8 +1663,8 @@ theend: /// - "c" |charwise| mode /// - "l" |linewise| mode /// - "" guess by contents, see |setreg()| -/// @param after Insert after cursor (like |p|), or before (like |P|). -/// @param follow Place cursor at end of inserted text. +/// @param after If true insert after cursor (like |p|), or before (like |P|). +/// @param follow If true place cursor at end of inserted text. /// @param[out] err Error details, if any void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err) @@ -2576,6 +2713,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); return rv; } @@ -2718,8 +2856,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) g = &pum_grid; } else if (grid > 1) { win_T *wp = get_win_by_grid_handle((handle_T)grid); - if (wp != NULL && wp->w_grid.chars != NULL) { - g = &wp->w_grid; + if (wp != NULL && wp->w_grid_alloc.chars != NULL) { + g = &wp->w_grid_alloc; } else { api_set_error(err, kErrorTypeValidation, "No grid with the given handle"); @@ -2748,19 +2886,6 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } -static void clear_provider(DecorProvider *p) -{ - if (p == NULL) { - return; - } - NLUA_CLEAR_REF(p->redraw_start); - NLUA_CLEAR_REF(p->redraw_buf); - NLUA_CLEAR_REF(p->redraw_win); - NLUA_CLEAR_REF(p->redraw_line); - NLUA_CLEAR_REF(p->redraw_end); - p->active = false; -} - /// Set or change decoration provider for a namespace /// /// This is a very general purpose interface for having lua callbacks @@ -2805,8 +2930,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { - DecorProvider *p = get_provider((NS)ns_id, true); - clear_provider(p); + DecorProvider *p = get_decor_provider((NS)ns_id, true); + decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? @@ -2828,7 +2953,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, String k = opts.items[i].key; Object *v = &opts.items[i].value; size_t j; - for (j = 0; cbs[j].name; j++) { + for (j = 0; cbs[j].name && cbs[j].dest; j++) { if (strequal(cbs[j].name, k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, @@ -2849,5 +2974,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, p->active = true; return; error: - clear_provider(p); + decor_provider_clear(p); } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f4af1632ec..89fa2f86fb 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -492,6 +492,35 @@ Dictionary nvim_win_get_config(Window window, Error *err) return rv; } +/// Closes the window and hide the buffer it contains (like |:hide| with a +/// |window-ID|). +/// +/// Like |:hide| the buffer becomes hidden unless another window is editing it, +/// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or +/// |nvim_win_close|, which will close the buffer. +/// +/// @param window Window handle, or 0 for current window +/// @param[out] err Error details, if any +void nvim_win_hide(Window window, Error *err) + FUNC_API_SINCE(7) + FUNC_API_CHECK_TEXTLOCK +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + + tabpage_T *tabpage = win_find_tabpage(win); + TryState tstate; + try_enter(&tstate); + if (tabpage == curtab) { + win_close(win, false); + } else { + win_close_othertab(win, false, tabpage); + } + vim_ignored = try_leave(&tstate, err); +} + /// Closes the window (like |:close| with a |window-ID|). /// /// @param window Window handle, or 0 for current window diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index f41068ea70..7e4dee3d34 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -31,7 +31,9 @@ #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" #define DCS 0x90 // Device Control String +#define DCS_STR "\033P" #define STERM 0x9c // String Terminator +#define STERM_STR "\033\\" #define POUND 0xA3 diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 42224d0a4f..145f6f5601 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -544,7 +544,7 @@ char_u *au_event_disable(char *what) } else { STRCAT(new_ei, what); } - set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE); + set_string_option_direct("ei", -1, new_ei, OPT_FREE, SID_NONE); xfree(new_ei); return save_ei; @@ -553,7 +553,7 @@ char_u *au_event_disable(char *what) void au_event_restore(char_u *old_ei) { if (old_ei != NULL) { - set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE); + set_string_option_direct("ei", -1, old_ei, OPT_FREE, SID_NONE); xfree(old_ei); } } @@ -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 { @@ -964,7 +968,7 @@ static int do_autocmd_event(event_T event, // Implementation of ":doautocmd [group] event [fname]". // Return OK for success, FAIL for failure; int do_doautocmd(char_u *arg, - int do_msg, // give message for no matching autocmds? + bool do_msg, // give message for no matching autocmds? bool *did_something) { char_u *fname; @@ -1013,11 +1017,12 @@ int do_doautocmd(char_u *arg, // ":doautoall": execute autocommands for each loaded buffer. void ex_doautoall(exarg_T *eap) { - int retval; + int retval = OK; aco_save_T aco; char_u *arg = eap->arg; int call_do_modelines = check_nomodeline(&arg); bufref_T bufref; + bool did_aucmd; // This is a bit tricky: For some commands curwin->w_buffer needs to be // equal to curbuf, but for some buffers there may not be a window. @@ -1025,14 +1030,14 @@ void ex_doautoall(exarg_T *eap) // gives problems when the autocommands make changes to the list of // buffers or windows... FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp == NULL) { + // Only do loaded buffers and skip the current buffer, it's done last. + if (buf->b_ml.ml_mfp == NULL || buf == curbuf) { continue; } // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); set_bufref(&bufref, buf); - bool did_aucmd; // execute the autocommands for this buffer retval = do_doautocmd(arg, false, &did_aucmd); @@ -1048,10 +1053,19 @@ void ex_doautoall(exarg_T *eap) // Stop if there is some error or buffer was deleted. if (retval == FAIL || !bufref_valid(&bufref)) { + retval = FAIL; break; } } + // Execute autocommands for the current buffer last. + if (retval == OK) { + (void)do_doautocmd(arg, false, &did_aucmd); + if (call_do_modelines && did_aucmd) { + do_modelines(0); + } + } + check_cursor(); // just in case lines got deleted } @@ -1178,10 +1192,10 @@ void aucmd_restbuf(aco_save_T *aco) win_remove(curwin, NULL); handle_unregister_window(curwin); - if (curwin->w_grid.chars != NULL) { - ui_comp_remove_grid(&curwin->w_grid); - ui_call_win_hide(curwin->w_grid.handle); - grid_free(&curwin->w_grid); + if (curwin->w_grid_alloc.chars != NULL) { + ui_comp_remove_grid(&curwin->w_grid_alloc); + ui_call_win_hide(curwin->w_grid_alloc.handle); + grid_free(&curwin->w_grid_alloc); } aucmd_win_used = false; @@ -1607,13 +1621,21 @@ static bool apply_autocmds_group(event_T event, ap->last = false; } ap->last = true; - check_lnums(true); // make sure cursor and topline are valid + + if (nesting == 1) { + // make sure cursor and topline are valid + check_lnums(true); + } // Execute the autocmd. The `getnextac` callback handles iteration. do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT); - reset_lnums(); // restore cursor and topline, unless they were changed + if (nesting == 1) { + // restore cursor and topline, unless they were changed + reset_lnums(); + } + if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); @@ -1657,11 +1679,13 @@ static bool apply_autocmds_group(event_T event, did_filetype = false; while (au_pending_free_buf != NULL) { buf_T *b = au_pending_free_buf->b_next; + xfree(au_pending_free_buf); au_pending_free_buf = b; } while (au_pending_free_win != NULL) { win_T *w = au_pending_free_win->w_next; + xfree(au_pending_free_win); au_pending_free_win = w; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 0134ee838d..ce4163fccf 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -282,7 +282,7 @@ int open_buffer( // Set/reset the Changed flag first, autocmds may change the buffer. // Apply the automatic commands, before processing the modelines. - // So the modelines have priority over auto commands. + // So the modelines have priority over autocommands. // When reading stdin, the buffer contents always needs writing, so set // the changed flag. Unless in readonly mode: "ls | nvim -R -". @@ -596,7 +596,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // Disable buffer-updates for the current buffer. // No need to check `unload_buf`: in that case the function returned above. - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); /* * Remove the buffer from the list. @@ -821,7 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); - buf_updates_unregister_all(buf); + buf_updates_unload(buf, false); } /* @@ -1726,7 +1726,8 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, file_id_valid)) != NULL) { xfree(ffname); if (lnum != 0) { - buflist_setfpos(buf, curwin, lnum, (colnr_T)0, false); + buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin, + lnum, (colnr_T)0, false); } if ((flags & BLN_NOOPT) == 0) { // Copy the options now, if 'cpo' doesn't have 's' and not done already. @@ -1843,7 +1844,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, EMSG(_("W14: Warning: List of file names overflow")); if (emsg_silent == 0) { ui_flush(); - os_delay(3000L, true); // make sure it is noticed + os_delay(3001L, true); // make sure it is noticed } top_file_num = 1; } @@ -1945,6 +1946,13 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_fo); clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); + clear_string_option(&buf->b_p_vsts); + xfree(buf->b_p_vsts_nopaste); + buf->b_p_vsts_nopaste = NULL; + xfree(buf->b_p_vsts_array); + buf->b_p_vsts_array = NULL; + clear_string_option(&buf->b_p_vts); + XFREE_CLEAR(buf->b_p_vts_array); clear_string_option(&buf->b_p_keymap); keymap_ga_clear(&buf->b_kmap_ga); ga_clear(&buf->b_kmap_ga); @@ -2308,6 +2316,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) *num_file = 0; // return values in case of FAIL *file = NULL; + if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff) { + return FAIL; + } + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". if (*pat == '^') { patc = xmalloc(STRLEN(pat) + 11); @@ -2344,6 +2356,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) if (!buf->b_p_bl) { // skip unlisted buffers continue; } + if (options & BUF_DIFF_FILTER) { + // Skip buffers not suitable for + // :diffget or :diffput completion. + if (buf == curbuf || !diff_mode_buf(buf)) { + continue; + } + } p = buflist_match(®match, buf, p_wic); if (p != NULL) { if (round == 1) { @@ -2486,6 +2505,7 @@ buflist_nr2name( /// /// @param[in,out] buf Buffer for which line and column are set. /// @param[in,out] win Window for which line and column are set. +/// May be NULL when using :badd. /// @param[in] lnum Line number to be set. If it is zero then only /// options are touched. /// @param[in] col Column number to be set. @@ -2493,7 +2513,7 @@ buflist_nr2name( void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T col, bool copy_options) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { wininfo_T *wip; @@ -2528,7 +2548,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, wip->wi_fpos.lnum = lnum; wip->wi_fpos.col = col; } - if (copy_options) { + if (copy_options && win != NULL) { // Save the window-specific option values. copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); wip->wi_fold_manual = win->w_fold_manual; @@ -2566,29 +2586,39 @@ static bool wininfo_other_tab_diff(wininfo_T *wip) return false; } -/* - * Find info for the current window in buffer "buf". - * If not found, return the info for the most recently used window. - * When "skip_diff_buffer" is true avoid windows with 'diff' set that is in - * another tab page. - * Returns NULL when there isn't any info. - */ -static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) +// Find info for the current window in buffer "buf". +// If not found, return the info for the most recently used window. +// When "need_options" is true skip entries where wi_optset is false. +// When "skip_diff_buffer" is true avoid windows with 'diff' set that is in +// another tab page. +// Returns NULL when there isn't any info. +static wininfo_T *find_wininfo(buf_T *buf, bool need_options, + bool skip_diff_buffer) + FUNC_ATTR_NONNULL_ALL { wininfo_T *wip; - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win == curwin && (!skip_diff_buffer || !wininfo_other_tab_diff(wip)) - ) + && (!need_options || wip->wi_optset)) { break; + } + } - /* If no wininfo for curwin, use the first in the list (that doesn't have - * 'diff' set and is in another tab page). */ + // If no wininfo for curwin, use the first in the list (that doesn't have + // 'diff' set and is in another tab page). + // If "need_options" is true skip entries that don't have options set, + // unless the window is editing "buf", so we can copy from the window + // itself. if (wip == NULL) { if (skip_diff_buffer) { for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (!wininfo_other_tab_diff(wip)) { + if (!wininfo_other_tab_diff(wip) + && (!need_options + || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) { break; } } @@ -2607,12 +2637,10 @@ static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) */ void get_winopts(buf_T *buf) { - wininfo_T *wip; - clear_winopt(&curwin->w_onebuf_opt); clearFolding(curwin); - wip = find_wininfo(buf, true); + wininfo_T *const wip = find_wininfo(buf, true, true); if (wip != NULL && wip->wi_win != curwin && wip->wi_win != NULL && wip->wi_win->w_buffer == buf) { win_T *wp = wip->wi_win; @@ -2649,7 +2677,7 @@ pos_T *buflist_findfpos(buf_T *buf) { static pos_T no_position = { 1, 0, 0 }; - wininfo_T *wip = find_wininfo(buf, false); + wininfo_T *const wip = find_wininfo(buf, false, false); return (wip == NULL) ? &no_position : &(wip->wi_fpos); } @@ -3236,7 +3264,7 @@ void maketitle(void) 0, maxlen, NULL, NULL); title_str = (char_u *)buf; if (called_emsg) { - set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"", + set_string_option_direct("titlestring", -1, (char_u *)"", OPT_FREE, SID_ERROR); } called_emsg |= save_called_emsg; @@ -3270,7 +3298,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) { @@ -3346,7 +3374,7 @@ void maketitle(void) p_iconstring, use_sandbox, 0, 0, NULL, NULL); if (called_emsg) { - set_string_option_direct((char_u *)"iconstring", -1, + set_string_option_direct("iconstring", -1, (char_u *)"", OPT_FREE, SID_ERROR); } called_emsg |= save_called_emsg; @@ -3486,7 +3514,7 @@ int build_stl_str_hl( stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); stl_groupitems = xmalloc(sizeof(int) * stl_items_len); stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); - stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len); stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } @@ -3940,9 +3968,9 @@ int build_stl_str_hl( // Store the current buffer number as a string variable vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); - set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp); + set_internal_string_var("g:actual_curbuf", buf_tmp); vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); - set_internal_string_var((char_u *)"g:actual_curwin", win_tmp); + set_internal_string_var("g:actual_curwin", win_tmp); buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; @@ -4482,22 +4510,15 @@ int build_stl_str_hl( int num_separators = 0; for (int i = 0; i < itemcnt; i++) { if (stl_items[i].type == Separate) { + // Create an array of the start location for each + // separator mark. + stl_separator_locations[num_separators] = i; num_separators++; } } // If we have separated groups, then we deal with it now if (num_separators) { - // Create an array of the start location for each - // separator mark. - int index = 0; - for (int i = 0; i < itemcnt; i++) { - if (stl_items[i].type == Separate) { - stl_separator_locations[index] = i; - index++; - } - } - int standard_spaces = (maxwidth - width) / num_separators; int final_spaces = (maxwidth - width) - standard_spaces * (num_separators - 1); @@ -5361,8 +5382,8 @@ bool bt_terminal(const buf_T *const buf) return buf != NULL && buf->b_p_bt[0] == 't'; } -// Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer. -// This means the buffer name is not a file name. +// Return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" +// buffer. This means the buffer name is not a file name. bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5372,7 +5393,8 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } -// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer. +// Return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" +// buffer. bool bt_dontwrite(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5467,20 +5489,20 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) int buf_signcols(buf_T *buf) { if (buf->b_signcols_max == -1) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the sign list buf->b_signcols_max = 0; int linesum = 0; linenr_T curline = 0; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum > curline) { + if (sign->se_lnum > curline) { if (linesum > buf->b_signcols_max) { buf->b_signcols_max = linesum; } - curline = sign->lnum; + curline = sign->se_lnum; linesum = 0; } - if (sign->has_text_or_icon) { + if (sign->se_has_text_or_icon) { linesum++; } } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index ee3fda5f6d..ac7ead5f92 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -33,6 +33,9 @@ enum bln_values { BLN_DUMMY = 4, // Allocating dummy buffer BLN_NEW = 8, // create a new buffer BLN_NOOPT = 16, // Don't copy options to existing buffer + // BLN_DUMMY_OK = 32, // also find an existing dummy buffer + // BLN_REUSE = 64, // may re-use number from buf_reuse + BLN_NOCURWIN = 128, // buffer is not associated with curwin }; // Values for action argument for do_buffer() diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index cc09b7e989..dd24db910e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -110,7 +110,7 @@ typedef uint16_t disptick_T; // display tick type #include "nvim/regexp_defs.h" // for synstate_T (needs reg_extmatch_T, win_T, buf_T) #include "nvim/syntax_defs.h" -// for signlist_T +// for sign_entry_T #include "nvim/sign_defs.h" #include "nvim/os/fs_defs.h" // for FileID @@ -296,13 +296,11 @@ typedef struct arglist { int id; ///< id of this arglist } alist_T; -/* - * For each argument remember the file name as it was given, and the buffer - * number that contains the expanded file name (required for when ":cd" is - * used. - * - * TODO: move aentry_T to another header - */ +// For each argument remember the file name as it was given, and the buffer +// number that contains the expanded file name (required for when ":cd" is +// used). +// +// TODO(Felipe): move aentry_T to another header typedef struct argentry { char_u *ae_fname; // file name as specified int ae_fnum; // buffer number with expanded file name @@ -490,11 +488,12 @@ typedef struct { LuaRef on_bytes; LuaRef on_changedtick; LuaRef on_detach; + LuaRef on_reload; bool utf_sizes; bool preview; } BufUpdateCallbacks; #define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, false, false } + LUA_NOREF, LUA_NOREF, false, false } EXTERN int curbuf_splice_pending INIT(= 0); @@ -744,6 +743,11 @@ struct file_buffer { long b_p_wm; ///< 'wrapmargin' long b_p_wm_nobin; ///< b_p_wm saved for binary mode long b_p_wm_nopaste; ///< b_p_wm saved for paste mode + char_u *b_p_vsts; ///< 'varsofttabstop' + long *b_p_vsts_array; ///< 'varsofttabstop' in internal format + char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode + char_u *b_p_vts; ///< 'vartabstop' + long *b_p_vts_array; ///< 'vartabstop' in internal format char_u *b_p_keymap; ///< 'keymap' // local values for options which are normally global @@ -844,7 +848,7 @@ struct file_buffer { // normally points to this, but some windows // may use a different synblock_T. - signlist_T *b_signlist; // list of signs to draw + sign_entry_T *b_signlist; // list of placed signs int b_signcols_max; // cached maximum number of sign columns int b_signcols; // last calculated number of sign columns @@ -1036,10 +1040,10 @@ struct matchitem { int id; ///< match ID int priority; ///< match priority char_u *pattern; ///< pattern to highlight - int hlg_id; ///< highlight group ID regmmatch_T match; ///< regexp program for pattern posmatch_T pos; ///< position matches match_T hl; ///< struct for doing the actual highlighting + int hlg_id; ///< highlight group ID int conceal_char; ///< cchar for Conceal highlighting }; @@ -1080,6 +1084,11 @@ typedef struct { bool external; bool focusable; WinStyle style; + bool border; + bool shadow; + schar_T border_chars[8]; + int border_hl_ids[8]; + int border_attr[8]; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1193,6 +1202,7 @@ struct window_S { int tab1; ///< first tab character int tab2; ///< second tab character int tab3; ///< third tab character + int lead; int trail; int conceal; } w_p_lcs_chars; @@ -1257,6 +1267,11 @@ struct window_S { int w_height_request; int w_width_request; + int w_border_adj[4]; // top, right, bottom, left + // outer size of window grid, including border + int w_height_outer; + int w_width_outer; + /* * === start of cached values ==== */ @@ -1332,7 +1347,8 @@ struct window_S { // w_redr_type is REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw - int w_redr_status; // if TRUE status line must be redrawn + bool w_redr_status; // if true status line must be redrawn + bool w_redr_border; // if true border must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1410,6 +1426,7 @@ struct window_S { int w_tagstacklen; // number of tags on stack ScreenGrid w_grid; // the grid specific to the window + ScreenGrid w_grid_alloc; // the grid specific to the window bool w_pos_changed; // true if window position changed bool w_floating; ///< whether the window is floating FloatConfig w_float_config; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 68e123896b..5c573530d1 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -135,7 +135,7 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid) } } -void buf_updates_unregister_all(buf_T *buf) +void buf_updates_unload(buf_T *buf, bool can_reload) { size_t size = kv_size(buf->update_channels); if (size) { @@ -146,9 +146,20 @@ void buf_updates_unregister_all(buf_T *buf) kv_init(buf->update_channels); } + size_t j = 0; for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); - if (cb.on_detach != LUA_NOREF) { + LuaRef thecb = LUA_NOREF; + + bool keep = false; + if (can_reload && cb.on_reload != LUA_NOREF) { + keep = true; + thecb = cb.on_reload; + } else if (cb.on_detach != LUA_NOREF) { + thecb = cb.on_detach; + } + + if (thecb != LUA_NOREF) { Array args = ARRAY_DICT_INIT; Object items[1]; args.size = 1; @@ -158,15 +169,24 @@ void buf_updates_unregister_all(buf_T *buf) args.items[0] = BUFFER_OBJ(buf->handle); textlock++; - nlua_call_ref(cb.on_detach, "detach", args, false, NULL); + nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL); textlock--; } - free_update_callbacks(cb); + + if (keep) { + kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); + } else { + buffer_update_callbacks_free(cb); + } + } + kv_size(buf->update_callbacks) = j; + if (kv_size(buf->update_callbacks) == 0) { + kv_destroy(buf->update_callbacks); + kv_init(buf->update_callbacks); } - kv_destroy(buf->update_callbacks); - kv_init(buf->update_callbacks); } + void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, @@ -270,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf, textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -322,7 +342,7 @@ void buf_updates_send_splice( textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } } @@ -358,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf) textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -386,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); } -static void free_update_callbacks(BufUpdateCallbacks cb) +void buffer_update_callbacks_free(BufUpdateCallbacks cb) { api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_bytes); api_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_reload); + api_free_luaref(cb.on_detach); } diff --git a/src/nvim/change.c b/src/nvim/change.c index 0f5081c94c..74e27ca880 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -68,7 +68,7 @@ void change_warning(int col) (void)msg_end(); if (msg_silent == 0 && !silent_mode && ui_active()) { ui_flush(); - os_delay(1000L, true); // give the user time to think about it + os_delay(1002L, true); // give the user time to think about it } curbuf->b_did_warn = true; redraw_cmdline = false; // don't redraw and erase the message @@ -109,7 +109,7 @@ void changed(void) // and don't let the emsg() set msg_scroll. if (need_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(2000L, true); + os_delay(2002L, true); wait_return(true); msg_scroll = save_msg_scroll; } else { @@ -828,6 +828,7 @@ int copy_indent(int size, char_u *src) int tab_pad; int ind_done; int round; + int ind_col; // Round 1: compute the number of characters needed for the indent // Round 2: copy the characters. @@ -835,13 +836,15 @@ int copy_indent(int size, char_u *src) todo = size; ind_len = 0; ind_done = 0; + ind_col = 0; s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { if (*s == TAB) { - tab_pad = (int)curbuf->b_p_ts - - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -849,9 +852,11 @@ int copy_indent(int size, char_u *src) } todo -= tab_pad; ind_done += tab_pad; + ind_col += tab_pad; } else { todo--; ind_done++; + ind_col++; } ind_len++; @@ -862,11 +867,12 @@ int copy_indent(int size, char_u *src) } // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, curbuf->b_p_vts_array); if ((todo >= tab_pad) && !curbuf->b_p_et) { todo -= tab_pad; ind_len++; + ind_col += tab_pad; if (p != NULL) { *p++ = TAB; @@ -874,12 +880,20 @@ int copy_indent(int size, char_u *src) } // Add tabs required for indent. - while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) { - todo -= (int)curbuf->b_p_ts; - ind_len++; - - if (p != NULL) { - *p++ = TAB; + if (!curbuf->b_p_et) { + for (;;) { + tab_pad = tabstop_padding(ind_col, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } + todo -= tab_pad; + ind_len++; + ind_col += tab_pad; + if (p != NULL) { + *p++ = TAB; + } } } @@ -1029,7 +1043,9 @@ int open_line( || do_si ) { // count white space on current line - newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false); + newindent = get_indent_str_vtab(saved_line, + curbuf->b_p_ts, + curbuf->b_p_vts_array, false); if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) { newindent = second_line_indent; // for ^^D command in insert mode } @@ -1453,7 +1469,9 @@ int open_line( if (curbuf->b_p_ai || do_si ) { - newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false); + newindent = get_indent_str_vtab(leader, + curbuf->b_p_ts, + curbuf->b_p_vts_array, false); } // Add the indent offset diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 37cbfb968b..22eb31513d 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -161,7 +161,7 @@ void channel_init(void) /// /// Channel is allocated with refcount 1, which should be decreased /// when the underlying stream closes. -static Channel *channel_alloc(ChannelStreamType type) +Channel *channel_alloc(ChannelStreamType type) { Channel *chan = xcalloc(1, sizeof(*chan)); if (type == kChannelStreamStdio) { @@ -292,7 +292,6 @@ static void close_cb(Stream *stream, void *data) /// directory if `cwd` is NULL /// @param[in] pty_width Width of the pty, ignored if `pty` is false /// @param[in] pty_height Height of the pty, ignored if `pty` is false -/// @param[in] term_name `$TERM` for the pty /// @param[in] env Nvim's configured environment is used if this is NULL, /// otherwise defines all environment variables /// @param[out] status_out 0 for invalid arguments, > 0 for the channel id, @@ -304,7 +303,7 @@ 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) + dict_T *env, varnumber_T *status_out) { assert(cwd == NULL || os_isdir_executable(cwd)); @@ -317,7 +316,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (detach) { EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); shell_free_argv(argv); - xfree(term_name); + if (env) { + tv_dict_free(env); + } channel_destroy_early(chan); *status_out = 0; return NULL; @@ -329,9 +330,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (pty_height > 0) { chan->stream.pty.height = pty_height; } - if (term_name) { - chan->stream.pty.term_name = term_name; - } } else { chan->stream.uv = libuv_process_init(&main_loop, chan); } @@ -358,17 +356,17 @@ 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->type == kProcessTypePty) { - xfree(chan->stream.pty.term_name); + if (proc->env) { + tv_dict_free(proc->env); } channel_destroy_early(chan); *status_out = proc->status; 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) { @@ -501,43 +499,54 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, } /// @param data will be consumed -size_t channel_send(uint64_t id, char *data, size_t len, const char **error) +size_t channel_send(uint64_t id, char *data, size_t len, + bool data_owned, const char **error) { Channel *chan = find_channel(id); + size_t written = 0; if (!chan) { - EMSG(_(e_invchan)); - goto err; + *error = _(e_invchan); + goto retfree; } if (chan->streamtype == kChannelStreamStderr) { if (chan->stream.err.closed) { *error = _("Can't send data to closed stream"); - goto err; + goto retfree; } // unbuffered write - size_t written = fwrite(data, len, 1, stderr); - xfree(data); - return len * written; + written = len * fwrite(data, len, 1, stderr); + goto retfree; + } + + if (chan->streamtype == kChannelStreamInternal && chan->term) { + terminal_receive(chan->term, data, len); + written = len; + goto retfree; } Stream *in = channel_instream(chan); if (in->closed) { *error = _("Can't send data to closed stream"); - goto err; + goto retfree; } if (chan->is_rpc) { *error = _("Can't send raw data to rpc channel"); - goto err; + goto retfree; } - WBuffer *buf = wstream_new_buffer(data, len, 1, xfree); + // write can be delayed indefinitely, so always use an allocated buffer + WBuffer *buf = wstream_new_buffer(data_owned ? data : xmemdup(data, len), + len, 1, xfree); return wstream_write(in, buf) ? len : 0; -err: - xfree(data); - return 0; +retfree: + if (data_owned) { + xfree(data); + } + return written; } /// Convert binary byte array to a readfile()-style list @@ -726,8 +735,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader) /// Open terminal for channel /// /// Channel `chan` is assumed to be an open pty channel, -/// and curbuf is assumed to be a new, unmodified buffer. -void channel_terminal_open(Channel *chan) +/// and `buf` is assumed to be a new, unmodified buffer. +void channel_terminal_open(buf_T *buf, Channel *chan) { TerminalOptions topts; topts.data = chan; @@ -736,8 +745,8 @@ void channel_terminal_open(Channel *chan) topts.write_cb = term_write; topts.resize_cb = term_resize; topts.close_cb = term_close; - curbuf->b_p_channel = (long)chan->id; // 'channel' option - Terminal *term = terminal_open(topts); + buf->b_p_channel = (long)chan->id; // 'channel' option + Terminal *term = terminal_open(buf, topts); chan->term = term; channel_incref(chan); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 3e52b3e3ce..e2d844a351 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -708,7 +708,7 @@ int ptr2cells(const char_u *p) /// @return number of character cells. int vim_strsize(char_u *s) { - return vim_strnsize(s, (int)MAXCOL); + return vim_strnsize(s, MAXCOL); } /// Return the number of character cells string "s[len]" will take on the @@ -744,8 +744,7 @@ int vim_strnsize(char_u *s, int len) /// @return Number of characters. #define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ if (*(p) == TAB && (!(wp)->w_p_list || wp->w_p_lcs_chars.tab1)) { \ - const int ts = (int)(buf)->b_p_ts; \ - return (ts - (int)(col % ts)); \ + return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \ } else { \ return ptr2cells(p); \ } @@ -1143,8 +1142,9 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) int n; if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - n = (int)wp->w_buffer->b_p_ts; - return n - (col % n); + return tabstop_padding(col, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array); } n = ptr2cells(s); @@ -1211,6 +1211,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, char_u *line; // start of the line int incr; int head; + long *vts = wp->w_buffer->b_p_vts_array; int ts = (int)wp->w_buffer->b_p_ts; int c; @@ -1251,7 +1252,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, // A tab gets expanded, depending on the current column if (c == TAB) { - incr = ts - (vcol % ts); + incr = tabstop_padding(vcol, ts, vts); } else { // For utf-8, if the byte is >= 0x80, need to look at // further bytes to find the cell width. @@ -1747,7 +1748,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 +1789,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/context.c b/src/nvim/context.c index 1ae0510762..4162daa6ca 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -126,7 +126,7 @@ bool ctx_restore(Context *ctx, const int flags) } char_u *op_shada; - get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL); + get_option_value("shada", NULL, &op_shada, OPT_GLOBAL); set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL); if (flags & kCtxRegs) { diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f3ee42fab1..e39d2328f5 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/vim.h" +#include "nvim/lua/executor.h" #include "nvim/extmark.h" #include "nvim/decoration.h" #include "nvim/screen.h" @@ -143,10 +144,9 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) state->row = -1; state->buf = buf; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = 0; @@ -188,20 +188,21 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) goto next_mark; } - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - HlRange range; if (mark.id&MARKTREE_END_FLAG) { - range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, decor->priority, vt, false }; + decor_add(state, altpos.row, altpos.col, mark.row, mark.col, + decor, false); } else { - range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, decor->priority, vt, false }; + if (altpos.row == -1) { + altpos.row = mark.row; + altpos.col = mark.col; + } + decor_add(state, mark.row, mark.col, altpos.row, altpos.col, + decor, false); } - hlrange_activate(range, state); next_mark: if (marktree_itr_node_done(state->itr)) { + marktree_itr_next(buf->b_marktree, state->itr); break; } marktree_itr_next(buf->b_marktree, state->itr); @@ -220,41 +221,37 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) return true; // TODO(bfredl): be more precise } -static void hlrange_activate(HlRange range, DecorState *state) +static void decor_add(DecorState *state, int start_row, int start_col, + int end_row, int end_col, Decoration *decor, bool owned) { - // Get size before preparing the push, to have the number of elements - size_t s = kv_size(state->active); - - kv_pushp(state->active); + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - size_t dest_index = 0; + DecorRange range = { start_row, start_col, end_row, end_col, + *decor, attr_id, + kv_size(decor->virt_text) && owned, -1 }; - // Determine insertion dest_index - while (dest_index < s) { - HlRange item = kv_A(state->active, dest_index); - if (item.priority > range.priority) { + kv_pushp(state->active); + size_t index; + for (index = kv_size(state->active)-1; index > 0; index--) { + DecorRange item = kv_A(state->active, index-1); + if (item.decor.priority <= range.decor.priority) { break; } - - dest_index++; - } - - // Splice - for (size_t index = s; index > dest_index; index--) { kv_A(state->active, index) = kv_A(state->active, index-1); } - - // Insert - kv_A(state->active, dest_index) = range; + kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, DecorState *state) +int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, + DecorState *state) { if (col <= state->col_until) { return state->current; } state->col_until = MAXCOL; while (true) { + // TODO(bfredl): check duplicate entry in "intersection" + // branch mtmark_t mark = marktree_itr_current(state->itr); if (mark.row < 0 || mark.row > state->row) { break; @@ -278,6 +275,11 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) } Decoration *decor = item->decor; + if (endpos.row == -1) { + endpos.row = mark.row; + endpos.col = mark.col; + } + if (endpos.row < mark.row || (endpos.row == mark.row && endpos.col <= mark.col)) { if (!kv_size(decor->virt_text)) { @@ -285,12 +287,8 @@ int decor_redraw_col(buf_T *buf, int col, DecorState *state) } } - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - hlrange_activate((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, decor->priority, - vt, false }, state); + decor_add(state, mark.row, mark.col, endpos.row, endpos.col, + decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -299,18 +297,18 @@ next_mark: int attr = 0; size_t j = 0; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); + DecorRange item = kv_A(state->active, i); bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { + if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { keep = false; } } else { if (item.start_row < state->row || (item.start_row == state->row && item.start_col <= col)) { active = true; - if (item.end_row == state->row) { + if (item.end_row == state->row && item.end_col > col) { state->col_until = MIN(state->col_until, item.end_col-1); } } else { @@ -322,11 +320,14 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } + if ((item.start_row == state->row && item.start_col <= col) + && kv_size(item.decor.virt_text) && item.virt_col == -1) { + item.virt_col = (item.decor.virt_text_hide && hidden) ? -2 : virt_col; + } if (keep) { - kv_A(state->active, j++) = kv_A(state->active, i); + kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = j; @@ -339,23 +340,81 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr) { - decor_redraw_col(buf, MAXCOL, state); + decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + VirtText text = VIRTTEXT_EMPTY; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text) { - return item.virt_text; + DecorRange item = kv_A(state->active, i); + if (!kv_size(text) + && item.start_row == state->row && kv_size(item.decor.virt_text) + && item.decor.virt_text_pos == kVTEndOfLine) { + text = item.decor.virt_text; + } + + if (item.decor.hl_eol && item.start_row <= state->row) { + *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return NULL; + + return text; +} + +void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, + Decoration *decor) +{ + if (end_row == -1) { + end_row = start_row; + end_col = start_col; + } + decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true); +} + + +DecorProvider *get_decor_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + } + DecorProvider *item = &kv_a(decor_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; } -void decor_add_ephemeral(int attr_id, int start_row, int start_col, - int end_row, int end_col, DecorPriority priority, - VirtText *virt_text) +void decor_provider_clear(DecorProvider *p) { -hlrange_activate(((HlRange){ start_row, start_col, end_row, end_col, attr_id, - priority, virt_text, virt_text != NULL }), - &decor_state); + if (p == NULL) { + return; + } + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +void decor_free_all_mem(void) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + decor_provider_clear(&kv_A(decor_providers, i)); + } + kv_destroy(decor_providers); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 2533a641dd..08d69060f0 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -18,29 +18,47 @@ typedef kvec_t(VirtTextChunk) VirtText; typedef uint16_t DecorPriority; #define DECOR_PRIORITY_BASE 0x1000 +typedef enum { + kVTEndOfLine, + kVTOverlay, +} VirtTextPos; + +typedef enum { + kHlModeUnknown, + kHlModeReplace, + kHlModeCombine, + kHlModeBlend, +} HlMode; + struct Decoration { int hl_id; // highlight group VirtText virt_text; + VirtTextPos virt_text_pos; + bool virt_text_hide; + HlMode hl_mode; + bool hl_eol; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free }; +#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ + kHlModeUnknown, false, DECOR_PRIORITY_BASE, false } typedef struct { int start_row; int start_col; int end_row; int end_col; - int attr_id; - DecorPriority priority; - VirtText *virt_text; + Decoration decor; + int attr_id; // cached lookup of decor.hl_id bool virt_text_owned; -} HlRange; + int virt_col; +} DecorRange; typedef struct { MarkTreeIter itr[1]; - kvec_t(HlRange) active; + kvec_t(DecorRange) active; buf_T *buf; int top_row; int row; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 93bc34fa4b..31b7b1bd8f 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -58,6 +58,7 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm #define DIFF_CLOSE_OFF 0x400 // diffoff when closing window +#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; @@ -1361,11 +1362,12 @@ void diff_win_options(win_T *wp, int addbuf) wp->w_p_crb_save = wp->w_p_crb; } wp->w_p_crb = true; - - if (!wp->w_p_diff) { - wp->w_p_wrap_save = wp->w_p_wrap; + if (!(diff_flags & DIFF_FOLLOWWRAP)) { + if (!wp->w_p_diff) { + wp->w_p_wrap_save = wp->w_p_wrap; + } + wp->w_p_wrap = false; } - wp->w_p_wrap = false; curwin = wp; // -V519 curbuf = curwin->w_buffer; @@ -1375,7 +1377,7 @@ void diff_win_options(win_T *wp, int addbuf) } wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); } - set_string_option_direct((char_u *)"fdm", -1, (char_u *)"diff", + set_string_option_direct("fdm", -1, (char_u *)"diff", OPT_LOCAL | OPT_FREE, 0); curwin = old_curwin; curbuf = curwin->w_buffer; @@ -1437,11 +1439,11 @@ void ex_diffoff(exarg_T *eap) if (wp->w_p_crb) { wp->w_p_crb = wp->w_p_crb_save; } - - if (!wp->w_p_wrap) { - wp->w_p_wrap = wp->w_p_wrap_save; + if (!(diff_flags & DIFF_FOLLOWWRAP)) { + if (!wp->w_p_wrap) { + wp->w_p_wrap = wp->w_p_wrap_save; + } } - free_string_option(wp->w_p_fdm); wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save ? wp->w_p_fdm_save @@ -2158,6 +2160,9 @@ int diffopt_changed(void) } else if (STRNCMP(p, "closeoff", 8) == 0) { p += 8; diff_flags_new |= DIFF_CLOSE_OFF; + } else if (STRNCMP(p, "followwrap", 10) == 0) { + p += 10; + diff_flags_new |= DIFF_FOLLOWWRAP; } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b2abb06075..f2fddc89fe 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 @@ -592,7 +597,10 @@ static int insert_check(VimState *state) s->mincol = curwin->w_wcol; validate_cursor_col(); - if (curwin->w_wcol < s->mincol - curbuf->b_p_ts + if ( + curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), + curbuf->b_p_ts, + curbuf->b_p_vts_array) && curwin->w_wrow == curwin->w_winrow + curwin->w_height_inner - 1 - get_scrolloff_value(curwin) && (curwin->w_cursor.lnum != curwin->w_topline @@ -1019,7 +1027,7 @@ static int insert_handle_key(InsertState *s) break; case K_EVENT: // some event - multiqueue_process_events(main_loop.events); + state_handle_k_event(); goto check_pum; case K_COMMAND: // some command @@ -1560,7 +1568,7 @@ void edit_putchar(int c, bool highlight) { int attr; - if (curwin->w_grid.chars != NULL || default_grid.chars != NULL) { + if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) { update_topline(curwin); // just in case w_topline isn't valid validate_cursor(); if (highlight) { @@ -1622,11 +1630,11 @@ static void init_prompt(int cmdchar_todo) ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); } curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); changed_bytes(curbuf->b_ml.ml_line_count, 0); } if (cmdchar_todo == 'A') { - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) { curwin->w_cursor.col = STRLEN(prompt); @@ -2050,7 +2058,7 @@ static bool check_compl_option(bool dict_opt) vim_beep(BO_COMPL); setcursor(); ui_flush(); - os_delay(2000L, false); + os_delay(2004L, false); } return false; } @@ -2311,7 +2319,11 @@ static int ins_compl_add(char_u *const str, int len, const Direction dir = (cdir == kDirectionNotSet ? compl_direction : cdir); int flags = flags_arg; - os_breakcheck(); + if (flags & CP_FAST) { + fast_breakcheck(); + } else { + os_breakcheck(); + } #define FREE_CPTEXT(cptext, cptext_allocated) \ do { \ if (cptext != NULL && cptext_allocated) { \ @@ -2515,7 +2527,8 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase) for (int i = 0; i < num_matches && add_r != FAIL; i++) { if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, - icase ? CP_ICASE : 0, false)) == OK) { + CP_FAST | (icase ? CP_ICASE : 0), + false)) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } @@ -2590,7 +2603,7 @@ void set_completion(colnr_T startcol, list_T *list) flags |= CP_ICASE; } if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false) != OK) { + flags | CP_FAST, false) != OK) { return; } @@ -3310,8 +3323,8 @@ static int ins_compl_bs(void) // allow the word to be deleted, we won't match everything. // Respect the 'backspace' option. if ((int)(p - line) - (int)compl_col < 0 - || ((int)(p - line) - (int)compl_col == 0 - && ctrl_x_mode != CTRL_X_OMNI) || ctrl_x_mode == CTRL_X_EVAL + || ((int)(p - line) - (int)compl_col == 0 && ctrl_x_mode != CTRL_X_OMNI) + || ctrl_x_mode == CTRL_X_EVAL || (!can_bs(BS_START) && (int)(p - line) - (int)compl_col - compl_length < 0)) { return K_BS; @@ -3926,7 +3939,7 @@ static void ins_compl_add_list(list_T *const list) // Go through the List with matches and add each of them. TV_LIST_ITER(list, li, { - if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir, true) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } else if (did_emsg) { @@ -3965,17 +3978,18 @@ static void ins_compl_add_dict(dict_T *dict) /// /// @param[in] tv Object to get matches from. /// @param[in] dir Completion direction. +/// @param[in] fast use fast_breakcheck() instead of os_breakcheck(). /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be /// returned in case of error. -int ins_compl_add_tv(typval_T *const tv, const Direction dir) +int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) FUNC_ATTR_NONNULL_ALL { const char *word; bool dup = false; bool empty = false; - int flags = 0; + int flags = fast ? CP_FAST : 0; char *(cptext[CPT_COUNT]); typval_T user_data; @@ -6241,9 +6255,10 @@ void auto_format( if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { // "cannot happen" curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); - } else + coladvance(MAXCOL); + } else { check_cursor_col(); + } // Insert mode: If the cursor is now after the end of the line while it // previously wasn't, the line was broken. Because of the rule above we @@ -7260,7 +7275,6 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) char_u *p; char_u *line; int icase; - int i; if (keytyped == NUL) { // Can happen with CTRL-Y and CTRL-E on a short line. @@ -7345,8 +7359,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) && p[curwin->w_cursor.col - 1] == ':' && p[curwin->w_cursor.col - 2] == ':') { p[curwin->w_cursor.col - 1] = ' '; - i = (cin_iscase(p, FALSE) || cin_isscopedecl(p) - || cin_islabel()); + const bool i = cin_iscase(p, false) + || cin_isscopedecl(p) + || cin_islabel(); p = get_cursor_line_ptr(); p[curwin->w_cursor.col - 1] = ':'; if (i) { @@ -8172,24 +8187,20 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) /* * Handle deleting one 'shiftwidth' or 'softtabstop'. */ - if ( mode == BACKSPACE_CHAR - && ((p_sta && in_indent) - || (get_sts_value() != 0 - && curwin->w_cursor.col > 0 - && (*(get_cursor_pos_ptr() - 1) == TAB - || (*(get_cursor_pos_ptr() - 1) == ' ' - && (!*inserted_space_p - || arrow_used)))))) { + if (mode == BACKSPACE_CHAR + && ((p_sta && in_indent) + || ((get_sts_value() != 0 + || tabstop_count(curbuf->b_p_vsts_array)) + && curwin->w_cursor.col > 0 + && (*(get_cursor_pos_ptr() - 1) == TAB + || (*(get_cursor_pos_ptr() - 1) == ' ' + && (!*inserted_space_p || arrow_used)))))) { int ts; colnr_T vcol; colnr_T want_vcol; colnr_T start_vcol; - *inserted_space_p = FALSE; - if (p_sta && in_indent) - ts = get_sw_value(curbuf); - else - ts = get_sts_value(); + *inserted_space_p = false; // Compute the virtual column where we want to be. Since // 'showbreak' may get in the way, need to get the last column of // the previous character. @@ -8198,7 +8209,14 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) dec_cursor(); getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol); inc_cursor(); - want_vcol = (want_vcol / ts) * ts; + if (p_sta && in_indent) { + ts = (int)get_sw_value(curbuf); + want_vcol = (want_vcol / ts) * ts; + } else { + want_vcol = tabstop_start(want_vcol, + get_sts_value(), + curbuf->b_p_vsts_array); + } // delete characters until we are at or before want_vcol while (vcol > want_vcol @@ -8427,8 +8445,8 @@ static void ins_left(void) // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); - --(curwin->w_cursor.lnum); - coladvance((colnr_T)MAXCOL); + curwin->w_cursor.lnum--; + coladvance(MAXCOL); curwin->w_set_curswant = true; // so we stay at the end } else { vim_beep(BO_CRSR); @@ -8462,7 +8480,7 @@ static void ins_end(int c) tpos = curwin->w_cursor; if (c == K_C_END) curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); curwin->w_curswant = MAXCOL; start_arrow(&tpos); @@ -8663,10 +8681,19 @@ static bool ins_tab(void) can_cindent = false; } - // When nothing special, insert TAB like a normal character + // When nothing special, insert TAB like a normal character. if (!curbuf->b_p_et - && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf)) - && get_sts_value() == 0) { + && !( + p_sta + && ind + // These five lines mean 'tabstop' != 'shiftwidth' + && ((tabstop_count(curbuf->b_p_vts_array) > 1) + || (tabstop_count(curbuf->b_p_vts_array) == 1 + && tabstop_first(curbuf->b_p_vts_array) + != get_sw_value(curbuf)) + || (tabstop_count(curbuf->b_p_vts_array) == 0 + && curbuf->b_p_ts != get_sw_value(curbuf)))) + && tabstop_count(curbuf->b_p_vsts_array) == 0 && get_sts_value() == 0) { return true; } @@ -8680,16 +8707,22 @@ static bool ins_tab(void) can_si_back = false; AppendToRedobuff("\t"); - if (p_sta && ind) { // insert tab in indent, use "shiftwidth" - temp = get_sw_value(curbuf); - } else if (curbuf->b_p_sts != 0) { // use "softtabstop" when set - temp = get_sts_value(); - } else { // otherwise use "tabstop" - temp = (int)curbuf->b_p_ts; + if (p_sta && ind) { // insert tab in indent, use 'shiftwidth' + temp = (int)get_sw_value(curbuf); + temp -= get_nolist_virtcol() % temp; + } else if (tabstop_count(curbuf->b_p_vsts_array) > 0 + || curbuf->b_p_sts != 0) { + // use 'softtabstop' when set + temp = tabstop_padding(get_nolist_virtcol(), + get_sts_value(), + curbuf->b_p_vsts_array); + } else { + // otherwise use 'tabstop' + temp = tabstop_padding(get_nolist_virtcol(), + curbuf->b_p_ts, + curbuf->b_p_vts_array); } - temp -= get_nolist_virtcol() % temp; - /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any @@ -8710,7 +8743,9 @@ static bool ins_tab(void) /* * When 'expandtab' not set: Replace spaces by TABs where possible. */ - if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) { + if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0 + || get_sts_value() > 0 + || (p_sta && ind))) { char_u *ptr; char_u *saved_line = NULL; // init for GCC pos_T pos; @@ -8807,6 +8842,11 @@ static bool ins_tab(void) replace_join(repl_off); } } + if (!(State & VREPLACE_FLAG)) { + extmark_splice_cols(curbuf, fpos.lnum - 1, change_col, + cursor->col - change_col, fpos.col - change_col, + kExtmarkUndo); + } } cursor->col -= i; @@ -9118,10 +9158,16 @@ static void ins_try_si(int c) * Get the value that w_virtcol would have when 'list' is off. * Unless 'cpo' contains the 'L' flag. */ -static colnr_T get_nolist_virtcol(void) +colnr_T get_nolist_virtcol(void) { - if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) + // check validity of cursor in current buffer + if (curwin->w_buffer == NULL || curwin->w_buffer->b_ml.ml_mfp == NULL + || curwin->w_cursor.lnum > curwin->w_buffer->b_ml.ml_line_count) { + return 0; + } + if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { return getvcol_nolist(&curwin->w_cursor); + } validate_virtcol(); return curwin->w_virtcol; } diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 09f401ee82..ef5dce738a 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -19,6 +19,7 @@ typedef enum { CP_CONT_S_IPOS = 4, // use CONT_S_IPOS for compl_cont_status CP_EQUAL = 8, // ins_compl_equal() always returns true CP_ICASE = 16, // ins_compl_equal ignores case + CP_FAST = 32, // use fast_breakcheck instead of os_breakcheck } cp_flags_T; typedef int (*IndentGetter)(void); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8a1556320c..05d429c7d5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -176,7 +176,6 @@ static struct vimvar { VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), - VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_REG, "register", VAR_STRING, VV_RO), VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), @@ -211,13 +210,9 @@ static struct vimvar { VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), VV(VV_ERRORS, "errors", VAR_LIST, 0), - VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), - VV(VV_EVENT, "event", VAR_DICT, VV_RO), VV(VV_FALSE, "false", VAR_BOOL, VV_RO), VV(VV_TRUE, "true", VAR_BOOL, VV_RO), VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), - VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), - VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), VV(VV_TESTING, "testing", VAR_NUMBER, 0), VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), @@ -227,10 +222,17 @@ static struct vimvar { VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + // Neovim + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), - VV(VV_ARGV, "argv", VAR_LIST, VV_RO), }; #undef VV @@ -365,7 +367,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; @@ -455,14 +457,15 @@ void eval_clear(void) * Set an internal variable to a string value. Creates the variable if it does * not already exist. */ -void set_internal_string_var(char_u *name, char_u *value) +void set_internal_string_var(const char *name, char_u *value) + FUNC_ATTR_NONNULL_ARG(1) { - const typval_T tv = { + typval_T tv = { .v_type = VAR_STRING, .vval.v_string = value, }; - set_var((const char *)name, STRLEN(name), (typval_T *)&tv, true); + set_var(name, strlen(name), &tv, true); } static lval_T *redir_lval = NULL; @@ -522,9 +525,9 @@ var_redir_start( tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; if (append) { - set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, true, false, "."); } else { - set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); err = did_emsg; @@ -584,7 +587,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, "."); } clear_lval(redir_lval); } @@ -914,6 +917,17 @@ varnumber_T eval_to_number(char_u *expr) return retval; } +// Top level evaluation function. +// Returns an allocated typval_T with the result. +// Returns NULL when there is an error. +typval_T *eval_expr(char_u *arg) +{ + typval_T *tv = xmalloc(sizeof(*tv)); + if (eval0(arg, tv, NULL, true) == FAIL) { + XFREE_CLEAR(tv); + } + return tv; +} /* * Prepare v: variable "idx" to be used. @@ -1847,7 +1861,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, s = tv_get_string_chk(tv); // != NULL if number or string. } if (s != NULL && op != NULL && *op != '=') { - opt_type = get_option_value(arg, &numval, (char_u **)&stringval, + opt_type = get_option_value((char *)arg, &numval, (char_u **)&stringval, opt_flags); if ((opt_type == 1 && *op == '.') || (opt_type == 0 && *op != '.')) { @@ -1924,7 +1938,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, is_const, op); + set_var_lval(&lv, p, tv, copy, is_const, (const char *)op); arg_end = p; } } @@ -2121,9 +2135,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, return NULL; } } - lp->ll_range = TRUE; - } else - lp->ll_range = FALSE; + lp->ll_range = true; + } else { + lp->ll_range = false; + } if (*p != ']') { if (!quiet) { @@ -2240,12 +2255,10 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, return NULL; } - /* - * May need to find the item or absolute index for the second - * index of a range. - * When no index given: "lp->ll_empty2" is TRUE. - * Otherwise "lp->ll_n2" is set to the second index. - */ + // May need to find the item or absolute index for the second + // index of a range. + // When no index given: "lp->ll_empty2" is true. + // Otherwise "lp->ll_n2" is set to the second index. if (lp->ll_range && !lp->ll_empty2) { lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string. tv_clear(&var2); @@ -2299,7 +2312,7 @@ void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const bool is_const, const char_u *op) + int copy, const bool is_const, const char *op) { int cc; listitem_T *ri; @@ -2326,7 +2339,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, TV_CSTRING) && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, TV_CSTRING))) - && eexe_mod_op(&tv, rettv, (const char *)op) == OK) { + && eexe_mod_op(&tv, rettv, op) == OK) { set_var(lp->ll_name, lp->ll_name_len, &tv, false); } tv_clear(&tv); @@ -2369,8 +2382,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, */ for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), - (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op); } else { tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); @@ -2428,7 +2440,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, } if (op != NULL && *op != '=') { - eexe_mod_op(lp->ll_tv, rettv, (const char *)op); + eexe_mod_op(lp->ll_tv, rettv, op); goto notify; } else { tv_clear(lp->ll_tv); @@ -3007,7 +3019,8 @@ static size_t varnamebuflen = 0; /* * Function to concatenate a prefix and a variable name. */ -static char_u *cat_prefix_varname(int prefix, char_u *name) +char_u *cat_prefix_varname(int prefix, const char_u *name) + FUNC_ATTR_NONNULL_ALL { size_t len = STRLEN(name) + 3; @@ -3128,21 +3141,6 @@ static int pattern_match(char_u *pat, char_u *text, bool ic) return matches; } -/* - * types for expressions. - */ -typedef enum { - TYPE_UNKNOWN = 0, - TYPE_EQUAL, // == - TYPE_NEQUAL, // != - TYPE_GREATER, // > - TYPE_GEQUAL, // >= - TYPE_SMALLER, // < - TYPE_SEQUAL, // <= - TYPE_MATCH, // =~ - TYPE_NOMATCH, // !~ -} exptype_T; - // TODO(ZyX-I): move to eval/expressions /* @@ -3419,11 +3417,8 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; char_u *p; - int i; - exptype_T type = TYPE_UNKNOWN; - bool type_is = false; // true for "is" and "isnot" + exprtype_T type = EXPR_UNKNOWN; int len = 2; - varnumber_T n1, n2; bool ic; /* @@ -3434,35 +3429,42 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) p = *arg; switch (p[0]) { - case '=': if (p[1] == '=') - type = TYPE_EQUAL; - else if (p[1] == '~') - type = TYPE_MATCH; + case '=': + if (p[1] == '=') { + type = EXPR_EQUAL; + } else if (p[1] == '~') { + type = EXPR_MATCH; + } break; - case '!': if (p[1] == '=') - type = TYPE_NEQUAL; - else if (p[1] == '~') - type = TYPE_NOMATCH; + case '!': + if (p[1] == '=') { + type = EXPR_NEQUAL; + } else if (p[1] == '~') { + type = EXPR_NOMATCH; + } break; - case '>': if (p[1] != '=') { - type = TYPE_GREATER; + case '>': + if (p[1] != '=') { + type = EXPR_GREATER; len = 1; - } else - type = TYPE_GEQUAL; + } else { + type = EXPR_GEQUAL; + } break; - case '<': if (p[1] != '=') { - type = TYPE_SMALLER; + case '<': + if (p[1] != '=') { + type = EXPR_SMALLER; len = 1; - } else - type = TYPE_SEQUAL; + } else { + type = EXPR_SEQUAL; + } break; case 'i': if (p[1] == 's') { if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') { len = 5; } if (!isalnum(p[len]) && p[len] != '_') { - type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL; - type_is = true; + type = len == 2 ? EXPR_IS : EXPR_ISNOT; } } break; @@ -3471,7 +3473,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) /* * If there is a comparative operator, use it. */ - if (type != TYPE_UNKNOWN) { + if (type != EXPR_UNKNOWN) { // extra question mark appended: ignore case if (p[len] == '?') { ic = true; @@ -3489,173 +3491,11 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) tv_clear(rettv); return FAIL; } - if (evaluate) { - if (type_is && rettv->v_type != var2.v_type) { - /* For "is" a different type always means FALSE, for "notis" - * it means TRUE. */ - n1 = (type == TYPE_NEQUAL); - } else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST) { - if (type_is) { - n1 = (rettv->v_type == var2.v_type - && rettv->vval.v_list == var2.vval.v_list); - if (type == TYPE_NEQUAL) - n1 = !n1; - } else if (rettv->v_type != var2.v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { - if (rettv->v_type != var2.v_type) { - EMSG(_("E691: Can only compare List with List")); - } else { - EMSG(_("E692: Invalid operation for List")); - } - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } else { - // Compare two Lists for being equal or unequal. - n1 = tv_list_equal(rettv->vval.v_list, var2.vval.v_list, ic, false); - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - } else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT) { - if (type_is) { - n1 = (rettv->v_type == var2.v_type - && rettv->vval.v_dict == var2.vval.v_dict); - if (type == TYPE_NEQUAL) - n1 = !n1; - } else if (rettv->v_type != var2.v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { - if (rettv->v_type != var2.v_type) - EMSG(_("E735: Can only compare Dictionary with Dictionary")); - else - EMSG(_("E736: Invalid operation for Dictionary")); - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } else { - // Compare two Dictionaries for being equal or unequal. - n1 = tv_dict_equal(rettv->vval.v_dict, var2.vval.v_dict, - ic, false); - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - } else if (tv_is_func(*rettv) || tv_is_func(var2)) { - if (type != TYPE_EQUAL && type != TYPE_NEQUAL) { - EMSG(_("E694: Invalid operation for Funcrefs")); - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } - if ((rettv->v_type == VAR_PARTIAL - && rettv->vval.v_partial == NULL) - || (var2.v_type == VAR_PARTIAL - && var2.vval.v_partial == NULL)) { - // when a partial is NULL assume not equal - n1 = false; - } else if (type_is) { - if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC) { - // strings are considered the same if their value is - // the same - n1 = tv_equal(rettv, &var2, ic, false); - } else if (rettv->v_type == VAR_PARTIAL - && var2.v_type == VAR_PARTIAL) { - n1 = (rettv->vval.v_partial == var2.vval.v_partial); - } else { - n1 = false; - } - } else { - n1 = tv_equal(rettv, &var2, ic, false); - } - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - /* - * If one of the two variables is a float, compare as a float. - * When using "=~" or "!~", always compare as string. - */ - else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { - float_T f1, f2; + const int ret = typval_compare(rettv, &var2, type, ic); - if (rettv->v_type == VAR_FLOAT) { - f1 = rettv->vval.v_float; - } else { - f1 = tv_get_number(rettv); - } - if (var2.v_type == VAR_FLOAT) { - f2 = var2.vval.v_float; - } else { - f2 = tv_get_number(&var2); - } - n1 = false; - switch (type) { - case TYPE_EQUAL: n1 = (f1 == f2); break; - case TYPE_NEQUAL: n1 = (f1 != f2); break; - case TYPE_GREATER: n1 = (f1 > f2); break; - case TYPE_GEQUAL: n1 = (f1 >= f2); break; - case TYPE_SMALLER: n1 = (f1 < f2); break; - case TYPE_SEQUAL: n1 = (f1 <= f2); break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; - } - } - /* - * If one of the two variables is a number, compare as a number. - * When using "=~" or "!~", always compare as string. - */ - else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { - n1 = tv_get_number(rettv); - n2 = tv_get_number(&var2); - switch (type) { - case TYPE_EQUAL: n1 = (n1 == n2); break; - case TYPE_NEQUAL: n1 = (n1 != n2); break; - case TYPE_GREATER: n1 = (n1 > n2); break; - case TYPE_GEQUAL: n1 = (n1 >= n2); break; - case TYPE_SMALLER: n1 = (n1 < n2); break; - case TYPE_SEQUAL: n1 = (n1 <= n2); break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; - } - } else { - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const s1 = tv_get_string_buf(rettv, buf1); - const char *const s2 = tv_get_string_buf(&var2, buf2); - if (type != TYPE_MATCH && type != TYPE_NOMATCH) { - i = mb_strcmp_ic(ic, s1, s2); - } else { - i = 0; - } - n1 = false; - switch (type) { - case TYPE_EQUAL: n1 = (i == 0); break; - case TYPE_NEQUAL: n1 = (i != 0); break; - case TYPE_GREATER: n1 = (i > 0); break; - case TYPE_GEQUAL: n1 = (i >= 0); break; - case TYPE_SMALLER: n1 = (i < 0); break; - case TYPE_SEQUAL: n1 = (i <= 0); break; - - case TYPE_MATCH: - case TYPE_NOMATCH: { - n1 = pattern_match((char_u *)s2, (char_u *)s1, ic); - if (type == TYPE_NOMATCH) { - n1 = !n1; - } - break; - } - case TYPE_UNKNOWN: break; // Avoid gcc warning. - } - } - tv_clear(rettv); tv_clear(&var2); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; + return ret; } } @@ -4538,7 +4378,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, c = *option_end; *option_end = NUL; - opt_type = get_option_value((char_u *)(*arg), &numval, + opt_type = get_option_value(*arg, &numval, rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == -3) { // invalid name @@ -5280,22 +5120,18 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, if (ht_stack == NULL) { abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); } else { - ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T)); - if (newitem == NULL) { - abort = true; - } else { - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; - } + ht_stack_T *const newitem = xmalloc(sizeof(ht_stack_T)); + newitem->ht = &dd->dv_hashtab; + newitem->prev = *ht_stack; + *ht_stack = newitem; } QUEUE *w = NULL; DictWatcher *watcher = NULL; - QUEUE_FOREACH(w, &dd->watchers) { + QUEUE_FOREACH(w, &dd->watchers, { watcher = tv_dict_watcher_node_data(w); set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack); - } + }) } break; } @@ -5308,14 +5144,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, if (list_stack == NULL) { abort = set_ref_in_list(ll, copyID, ht_stack); } else { - list_stack_T *newitem = try_malloc(sizeof(list_stack_T)); - if (newitem == NULL) { - abort = true; - } else { - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; - } + list_stack_T *const newitem = xmalloc(sizeof(list_stack_T)); + newitem->list = ll; + newitem->prev = *list_stack; + *list_stack = newitem; } } break; @@ -5874,26 +5706,53 @@ int assert_inrange(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { bool error = false; - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); + if (argvars[0].v_type == VAR_FLOAT + || argvars[1].v_type == VAR_FLOAT + || argvars[2].v_type == VAR_FLOAT) { + const float_T flower = tv_get_float(&argvars[0]); + const float_T fupper = tv_get_float(&argvars[1]); + const float_T factual = tv_get_float(&argvars[2]); - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; + if (factual < flower || factual > fupper) { + garray_T ga; + prepare_assert_error(&ga); + if (argvars[3].v_type != VAR_UNKNOWN) { + char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, tofree); + xfree(tofree); + } else { + char msg[80]; + vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", + flower, fupper, factual); + ga_concat(&ga, (char_u *)msg); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } else { + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); + + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; + prepare_assert_error(&ga); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); + assert_error(&ga); + ga_clear(&ga); + return 1; + } } return 0; } @@ -5961,6 +5820,35 @@ static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, } } +int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, (const char_u *)"command did beep: "); + } else { + ga_concat(&ga, (const char_u *)"command did not beep: "); + } + ga_concat(&ga, (const char_u *)cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + int assert_fails(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { @@ -6214,6 +6102,7 @@ void common_function(typval_T *argvars, typval_T *rettv, // function(dict.MyFunc, [arg]) arg_pt = argvars[0].vval.v_partial; s = partial_name(arg_pt); + // TODO(bfredl): do the entire nlua_is_table_from_lua dance } else { // function('MyFunc', [arg], dict) s = (char_u *)tv_get_string(&argvars[0]); @@ -7313,7 +7202,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) char_u *name = nlua_register_table_as_callable(arg); if (name != NULL) { - func_ref(name); callback->data.funcref = vim_strsave(name); callback->type = kCallbackFuncref; } else { @@ -7951,8 +7839,8 @@ int get_id_len(const char **const arg) */ int get_name_len(const char **const arg, char **alias, - int evaluate, - int verbose) + bool evaluate, + bool verbose) { int len; @@ -8437,10 +8325,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) return oldval; } -/* - * Get the value of internal variable "name". - * Return OK or FAIL. - */ +// Get the value of internal variable "name". +// Return OK or FAIL. If OK is returned "rettv" must be cleared. int get_var_tv( const char *name, int len, // length of "name" @@ -8508,7 +8394,7 @@ static bool tv_is_luafunc(typval_T *tv) int check_luafunc_name(const char *str, bool paren) { const char *p = str; - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { p++; } if (*p != (paren ? '(' : NUL)) { @@ -10697,3 +10583,209 @@ bool invoke_prompt_interrupt(void) tv_clear(&rettv); return true; } + +// Compare "typ1" and "typ2". Put the result in "typ1". +int typval_compare( + typval_T *typ1, // first operand + typval_T *typ2, // second operand + exprtype_T type, // operator + bool ic // ignore case +) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n1, n2; + const bool type_is = type == EXPR_IS || type == EXPR_ISNOT; + + if (type_is && typ1->v_type != typ2->v_type) { + // For "is" a different type always means false, for "notis" + // it means true. + n1 = type == EXPR_ISNOT; + } else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) { + if (type_is) { + n1 = typ1->v_type == typ2->v_type + && typ1->vval.v_list == typ2->vval.v_list; + if (type == EXPR_ISNOT) { + n1 = !n1; + } + } else if (typ1->v_type != typ2->v_type + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { + if (typ1->v_type != typ2->v_type) { + EMSG(_("E691: Can only compare List with List")); + } else { + EMSG(_("E692: Invalid operation for List")); + } + tv_clear(typ1); + return FAIL; + } else { + // Compare two Lists for being equal or unequal. + n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false); + if (type == EXPR_NEQUAL) { + n1 = !n1; + } + } + } else if (typ1->v_type == VAR_DICT || typ2->v_type == VAR_DICT) { + if (type_is) { + n1 = typ1->v_type == typ2->v_type + && typ1->vval.v_dict == typ2->vval.v_dict; + if (type == EXPR_ISNOT) { + n1 = !n1; + } + } else if (typ1->v_type != typ2->v_type + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { + if (typ1->v_type != typ2->v_type) { + EMSG(_("E735: Can only compare Dictionary with Dictionary")); + } else { + EMSG(_("E736: Invalid operation for Dictionary")); + } + tv_clear(typ1); + return FAIL; + } else { + // Compare two Dictionaries for being equal or unequal. + n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false); + if (type == EXPR_NEQUAL) { + n1 = !n1; + } + } + } else if (tv_is_func(*typ1) || tv_is_func(*typ2)) { + if (type != EXPR_EQUAL && type != EXPR_NEQUAL + && type != EXPR_IS && type != EXPR_ISNOT) { + EMSG(_("E694: Invalid operation for Funcrefs")); + tv_clear(typ1); + return FAIL; + } + if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL) + || (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) { + // when a partial is NULL assume not equal + n1 = false; + } else if (type_is) { + if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) { + // strings are considered the same if their value is + // the same + n1 = tv_equal(typ1, typ2, ic, false); + } else if (typ1->v_type == VAR_PARTIAL && typ2->v_type == VAR_PARTIAL) { + n1 = typ1->vval.v_partial == typ2->vval.v_partial; + } else { + n1 = false; + } + } else { + n1 = tv_equal(typ1, typ2, ic, false); + } + if (type == EXPR_NEQUAL || type == EXPR_ISNOT) { + n1 = !n1; + } + } else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT) + && type != EXPR_MATCH && type != EXPR_NOMATCH) { + // If one of the two variables is a float, compare as a float. + // When using "=~" or "!~", always compare as string. + const float_T f1 = tv_get_float(typ1); + const float_T f2 = tv_get_float(typ2); + n1 = false; + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = f1 == f2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = f1 != f2; break; + case EXPR_GREATER: n1 = f1 > f2; break; + case EXPR_GEQUAL: n1 = f1 >= f2; break; + case EXPR_SMALLER: n1 = f1 < f2; break; + case EXPR_SEQUAL: n1 = f1 <= f2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning + } + } else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER) + && type != EXPR_MATCH && type != EXPR_NOMATCH) { + // If one of the two variables is a number, compare as a number. + // When using "=~" or "!~", always compare as string. + n1 = tv_get_number(typ1); + n2 = tv_get_number(typ2); + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = n1 == n2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = n1 != n2; break; + case EXPR_GREATER: n1 = n1 > n2; break; + case EXPR_GEQUAL: n1 = n1 >= n2; break; + case EXPR_SMALLER: n1 = n1 < n2; break; + case EXPR_SEQUAL: n1 = n1 <= n2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning + } + } else { + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const s1 = tv_get_string_buf(typ1, buf1); + const char *const s2 = tv_get_string_buf(typ2, buf2); + int i; + if (type != EXPR_MATCH && type != EXPR_NOMATCH) { + i = mb_strcmp_ic(ic, s1, s2); + } else { + i = 0; + } + n1 = false; + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = i == 0; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = i != 0; break; + case EXPR_GREATER: n1 = i > 0; break; + case EXPR_GEQUAL: n1 = i >= 0; break; + case EXPR_SMALLER: n1 = i < 0; break; + case EXPR_SEQUAL: n1 = i <= 0; break; + + case EXPR_MATCH: + case EXPR_NOMATCH: + n1 = pattern_match((char_u *)s2, (char_u *)s1, ic); + if (type == EXPR_NOMATCH) { + n1 = !n1; + } + break; + case EXPR_UNKNOWN: break; // avoid gcc warning + } + } + tv_clear(typ1); + typ1->v_type = VAR_NUMBER; + typ1->vval.v_number = n1; + return OK; +} + +char *typval_tostring(typval_T *arg) +{ + if (arg == NULL) { + return xstrdup("(does not exist)"); + } + return encode_tv2string(arg, NULL); +} + +bool var_exists(const char *var) + FUNC_ATTR_NONNULL_ALL +{ + char *tofree; + bool n = false; + + // get_name_len() takes care of expanding curly braces + const char *name = var; + const int len = get_name_len((const char **)&var, &tofree, true, false); + if (len > 0) { + typval_T tv; + + if (tofree != NULL) { + name = tofree; + } + n = get_var_tv(name, len, &tv, NULL, false, true) == OK; + if (n) { + // Handle d.key, l[idx], f(expr). + n = handle_subscript(&var, &tv, true, false) == OK; + if (n) { + tv_clear(&tv); + } + } + } + if (*var != NUL) { + n = false; + } + + xfree(tofree); + return n; +} diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 06b7f9e21d..3da4bb8655 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -56,10 +56,10 @@ typedef struct lval_S { ///< isn't NULL it's the Dict to which to add the item. listitem_T *ll_li; ///< The list item or NULL. list_T *ll_list; ///< The list or NULL. - int ll_range; ///< TRUE when a [i:j] range was used. + bool ll_range; ///< true when a [i:j] range was used. + bool ll_empty2; ///< Second index is empty: [i:]. long ll_n1; ///< First index for list. long ll_n2; ///< Second index for list range. - int ll_empty2; ///< Second index is empty: [i:]. dict_T *ll_dict; ///< The Dictionary or NULL. dictitem_T *ll_di; ///< The dictitem or NULL. char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. @@ -105,7 +105,6 @@ typedef enum { VV_DYING, VV_EXCEPTION, VV_THROWPOINT, - VV_STDERR, VV_REG, VV_CMDBANG, VV_INSERTMODE, @@ -140,13 +139,9 @@ typedef enum { VV_OPTION_OLD, VV_OPTION_TYPE, VV_ERRORS, - VV_MSGPACK_TYPES, - VV_EVENT, VV_FALSE, VV_TRUE, VV_NULL, - VV__NULL_LIST, // List with NULL value. For test purposes only. - VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_VIM_DID_ENTER, VV_TESTING, VV_TYPE_NUMBER, @@ -156,10 +151,17 @@ typedef enum { VV_TYPE_DICT, VV_TYPE_FLOAT, VV_TYPE_BOOL, + VV_EVENT, VV_ECHOSPACE, + VV_ARGV, VV_EXITING, + // Neovim + VV_STDERR, + VV_MSGPACK_TYPES, + VV__NULL_STRING, // String with NULL value. For test purposes only. + VV__NULL_LIST, // List with NULL value. For test purposes only. + VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_LUA, - VV_ARGV, } VimVarIndex; /// All recognized msgpack types @@ -226,6 +228,21 @@ typedef enum ASSERT_OTHER, } assert_type_T; +/// types for expressions. +typedef enum { + EXPR_UNKNOWN = 0, + EXPR_EQUAL, ///< == + EXPR_NEQUAL, ///< != + EXPR_GREATER, ///< > + EXPR_GEQUAL, ///< >= + EXPR_SMALLER, ///< < + EXPR_SEQUAL, ///< <= + EXPR_MATCH, ///< =~ + EXPR_NOMATCH, ///< !~ + EXPR_IS, ///< is + EXPR_ISNOT, ///< isnot +} exprtype_T; + /// Type for dict_list function typedef enum { kDictListKeys, ///< List dictionary keys. diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 952fa35b83..b10e99fc08 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -26,7 +26,7 @@ return { arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1, 2}}, + assert_beeps={args={1}}, assert_equal={args={2, 3}}, assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, @@ -34,6 +34,7 @@ return { assert_false={args={1, 2}}, assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, + assert_nobeep={args={1}}, assert_notequal={args={2, 3}}, assert_notmatch={args={2, 3}}, assert_report={args=1}, @@ -63,6 +64,7 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}}, + charidx={args={2, 3}}, cindent={args=1}, clearmatches={args={0, 1}}, col={args=1}, @@ -308,14 +310,16 @@ return { setwinvar={args=3}, sha256={args=1}, shellescape={args={1, 2}}, - shiftwidth={}, + shiftwidth={args={0, 1}}, sign_define={args={1, 2}}, sign_getdefined={args={0, 1}}, sign_getplaced={args={0, 2}}, sign_jump={args={3, 3}}, sign_place={args={4, 5}}, + sign_placelist={args={1}}, sign_undefine={args={0, 1}}, sign_unplace={args={1, 2}}, + sign_unplacelist={args={1}}, simplify={args=1}, sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, @@ -340,6 +344,7 @@ return { string={args=1}, strlen={args=1}, strpart={args={2, 4}}, + strptime={args=2}, strridx={args={2, 3}}, strtrans={args=1}, strwidth={args=1}, @@ -391,6 +396,7 @@ return { win_id2tabwin={args=1}, win_id2win={args=1}, win_screenpos={args=1}, + win_splitmove={args={2, 3}}, winbufnr={args=1}, wincol={}, windowsversion={}, 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..0d288e2cc2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -117,8 +117,12 @@ char_u *get_function_name(expand_T *xp, int idx) intidx = -1; if (intidx < 0) { name = get_user_func_name(xp, idx); - if (name != NULL) + if (name != NULL) { + if (*name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); + } return name; + } } while ((size_t)++intidx < ARRAY_SIZE(functions) && functions[intidx].name[0] == '\0') { @@ -387,28 +391,16 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "assert_beeps(cmd [, error])" function static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } + rettv->vval.v_number = assert_beeps(argvars, false); +} - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; +// "assert_nobeep(cmd [, error])" function +static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); } // "assert_equal(expected, actual[, msg])" function @@ -818,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + bool owned = false; char_u *func; partial_T *partial = NULL; dict_T *selfdict = NULL; @@ -828,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { func = nlua_register_table_as_callable(&argvars[0]); + owned = true; } else { func = (char_u *)tv_get_string(&argvars[0]); } @@ -845,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } func_call(func, &argvars[1], partial, selfdict, rettv); + if (owned) { + func_unref(func); + } } /* @@ -919,7 +916,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } uint64_t id = argvars[0].vval.v_number; const char *error = NULL; - rettv->vval.v_number = channel_send(id, input, input_len, &error); + rettv->vval.v_number = channel_send(id, input, input_len, true, &error); if (error) { EMSG(error); } @@ -940,6 +937,52 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) (const char_u *)tv_get_string(&argvars[0])); } +// "charidx()" function +static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING + || argvars[1].v_type != VAR_NUMBER + || (argvars[2].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_NUMBER)) { + EMSG(_(e_invarg)); + return; + } + + const char *str = tv_get_string_chk(&argvars[0]); + varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); + if (str == NULL || idx < 0) { + return; + } + int countcc = 0; + if (argvars[2].v_type != VAR_UNKNOWN) { + countcc = (int)tv_get_number(&argvars[2]); + } + if (countcc < 0 || countcc > 1) { + EMSG(_(e_invarg)); + return; + } + + int (*ptr2len)(const char_u *); + if (countcc) { + ptr2len = utf_ptr2len; + } else { + ptr2len = utfc_ptr2len; + } + + const char *p; + int len; + for (p = str, len = 0; p <= str + idx; len++) { + if (*p == NUL) { + return; + } + p += ptr2len((const char_u *)p); + } + + rettv->vval.v_number = len > 0 ? len - 1 : 0; +} + /* * "cindent(lnum)" function */ @@ -1055,7 +1098,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); + rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } /* @@ -1629,27 +1672,26 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (u_save(first - 1, last + 1) == FAIL) { rettv->vval.v_number = 1; // FAIL - return; - } - - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } + } else { + for (linenr_T lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; - } else if (wp->w_cursor.lnum> first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= count; + } else if (wp->w_cursor.lnum> first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } } } + check_cursor_col(); + deleted_lines_mark(first, count); } - check_cursor_col(); - deleted_lines_mark(first, count); if (!is_curbuf) { curbuf = curbuf_save; @@ -1798,7 +1840,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 +1848,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); @@ -2003,7 +2051,6 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = false; - int len = 0; const char *p = tv_get_string(&argvars[0]); if (*p == '$') { // Environment variable. @@ -2034,29 +2081,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = au_exists(p + 1); } } else { // Internal variable. - typval_T tv; - - // get_name_len() takes care of expanding curly braces - const char *name = p; - char *tofree; - len = get_name_len((const char **)&p, &tofree, true, false); - if (len > 0) { - if (tofree != NULL) { - name = tofree; - } - n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); - if (n) { - // Handle d.key, l[idx], f(expr). - n = (handle_subscript(&p, &tv, true, false) == OK); - if (n) { - tv_clear(&tv); - } - } - } - if (*p != NUL) - n = FALSE; - - xfree(tofree); + n = var_exists(p); } rettv->vval.v_number = n; @@ -2143,7 +2168,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { - const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); + const char *const strmodes = tv_get_string(&argvars[1]); modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); } menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); @@ -2978,10 +3003,11 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. + // TODO(bfredl): deduplicate shared logic with state_enter ? if (!(char_avail() || using_script() || input_available())) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); continue; } } @@ -3129,6 +3155,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH | WILD_NO_BEEP; + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "type must be a string"); + return; + } + const char *const type = tv_get_string(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { filtered = (bool)tv_get_number_chk(&argvars[2], NULL); } @@ -3142,12 +3174,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) options |= WILD_KEEP_ALL; } - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + if (argvars[0].v_type != VAR_STRING) { EMSG(_(e_invarg)); return; } - if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { + if (strcmp(type, "cmdline") == 0) { set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); goto theend; @@ -3156,15 +3188,14 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) ExpandInit(&xpc); xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type( - (char_u *)tv_get_string(&argvars[1])); + xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { - EMSG2(_(e_invarg2), argvars[1].vval.v_string); + EMSG2(_(e_invarg2), type); return; } if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); + set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); } @@ -3301,7 +3332,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) { @@ -3919,6 +3950,87 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } +// +// Move the window wp into a new split of targetwin in a given direction +// +static void win_move_into_split(win_T *wp, win_T *targetwin, + int size, int flags) +{ + int dir; + int height = wp->w_height; + win_T *oldwin = curwin; + + if (wp == targetwin) { + return; + } + + // Jump to the target window + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Remove the old window and frame from the tree of frames + (void)winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + (void)win_comp_pos(); // recompute window positions + + // Split a window on the desired side and put the old window there + (void)win_split_ins(size, flags, wp, dir); + + // If splitting horizontally, try to preserve height + if (size == 0 && !(flags & WSP_VERT)) { + win_setheight_win(height, wp); + if (p_ea) { + win_equal(wp, true, 'v'); + } + } + + if (oldwin != curwin) { + win_goto(oldwin); + } +} + +// "win_splitmove()" function +static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + win_T *targetwin; + int flags = 0, size = 0; + + wp = find_win_by_nr_or_id(&argvars[0]); + targetwin = find_win_by_nr_or_id(&argvars[1]); + + if (wp == NULL || targetwin == NULL || wp == targetwin + || !win_valid(wp) || !win_valid(targetwin) + || win_valid_floating(wp) || win_valid_floating(targetwin)) { + EMSG(_(e_invalwindow)); + rettv->vval.v_number = -1; + return; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + dict_T *d; + dictitem_T *di; + + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + EMSG(_(e_invarg)); + return; + } + + d = argvars[2].vval.v_dict; + if (tv_dict_get_number(d, "vertical")) { + flags |= WSP_VERT; + } + if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { + flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; + } + size = tv_dict_get_number(d, "size"); + } + + win_move_into_split(wp, targetwin, size, flags); +} + // "getwinpos({timeout})" function static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4075,7 +4187,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef _WIN64 "win64", #endif +#ifndef CASE_INSENSITIVE_FILENAME "fname_case", +#endif #ifdef HAVE_ACL "acl", #endif @@ -4168,6 +4282,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "title", "user-commands", // was accidentally included in 5.4 "user_commands", + "vartabs", "vertsplit", "virtualedit", "visual", @@ -4354,7 +4469,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 +4990,108 @@ 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, + const char * const pty_term_name) +{ + 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 + } + } + + // For a pty, we need a sane $TERM set. We can't rely on nvim's environment, + // because the child process is going to be communicating with nvim, not the + // parent terminal. Set a sane default, but let the user override it in the + // job's environment if they want. + if (pty) { + dictitem_T *dv = tv_dict_find(env, S_LEN("TERM")); + if (dv) { + tv_dict_item_remove(env, dv); + } + tv_dict_add_str(env, S_LEN("TERM"), pty_term_name); + } + + 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 +5104,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 +5128,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 +5154,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,46 +5163,14 @@ 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; @@ -4997,12 +5183,19 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (pty) { width = (uint16_t)tv_dict_get_number(job_opts, "width"); height = (uint16_t)tv_dict_get_number(job_opts, "height"); - term_name = tv_dict_get_string(job_opts, "TERM", true); + // Legacy method, before env option existed, to specify $TERM. No longer + // documented, but still usable to avoid breaking scripts. + term_name = tv_dict_get_string(job_opts, "TERM", false); + if (!term_name) { + term_name = "ansi"; + } } + env = create_environment(job_env, clear_env, pty, term_name); + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, cwd, width, height, - term_name, env, &rettv->vval.v_number); + env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -7445,7 +7638,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, false, true, false, false, NULL, 0, 0, - NULL, NULL, &rettv->vval.v_number); + NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -8587,8 +8780,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (set_tagstack(wp, d, action) == OK) { rettv->vval.v_number = 0; - } else { - EMSG(_(e_listreq)); } } @@ -8628,6 +8819,18 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + rettv->vval.v_number = 0; + + if (argvars[0].v_type != VAR_UNKNOWN) { + long col; + + col = (long)tv_get_number_chk(argvars, NULL); + if (col < 0) { + return; // type error; errmsg already given + } + rettv->vval.v_number = get_sw_value_col(curbuf, col); + return; + } rettv->vval.v_number = get_sw_value(curbuf); } @@ -8635,56 +8838,30 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *name; - dict_T *dict; - char *icon = NULL; - char *linehl = NULL; - char *text = NULL; - char *texthl = NULL; - char *numhl = NULL; - rettv->vval.v_number = -1; + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Define multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { + sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); return; } - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } + // Define a single sign + rettv->vval.v_number = -1; - // sign attributes - dict = argvars[1].vval.v_dict; - if (tv_dict_find(dict, "icon", -1) != NULL) { - icon = tv_dict_get_string(dict, "icon", true); - } - if (tv_dict_find(dict, "linehl", -1) != NULL) { - linehl = tv_dict_get_string(dict, "linehl", true); - } - if (tv_dict_find(dict, "text", -1) != NULL) { - text = tv_dict_get_string(dict, "text", true); - } - if (tv_dict_find(dict, "texthl", -1) != NULL) { - texthl = tv_dict_get_string(dict, "texthl", true); - } - if (tv_dict_find(dict, "numhl", -1) != NULL) { - numhl = tv_dict_get_string(dict, "numhl", true); - } + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; } - if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, (char_u *)numhl) - == OK) { - rettv->vval.v_number = 0; + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; } - xfree(icon); - xfree(linehl); - xfree(text); - xfree(texthl); - xfree(numhl); + rettv->vval.v_number = sign_define_from_dict( + name, argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL); } /// "sign_getdefined()" function @@ -8769,7 +8946,7 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; - // Sign identifer + // Sign identifier sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); if (notanum) { return; @@ -8805,83 +8982,44 @@ cleanup: /// "sign_place()" function static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int sign_id; - char_u *group = NULL; - const char *sign_name; - buf_T *buf; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int prio = SIGN_DEF_PRIO; - bool notanum = false; + dict_T *dict = NULL; rettv->vval.v_number = -1; - // Sign identifer - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id < 0) { - EMSG(_(e_invarg)); + if (argvars[4].v_type != VAR_UNKNOWN + && (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL))) { + EMSG(_(e_dictreq)); return; } - // Sign group - const char *group_chk = tv_get_string_chk(&argvars[1]); - if (group_chk == NULL) { - return; - } - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } + rettv->vval.v_number = sign_place_from_dict( + &argvars[0], &argvars[1], &argvars[2], &argvars[3], dict); +} - // Sign name - sign_name = tv_get_string_chk(&argvars[2]); - if (sign_name == NULL) { - goto cleanup; - } +/// "sign_placelist()" function. Place multiple signs. +static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; - // Buffer to place the sign - buf = get_buf_arg(&argvars[3]); - if (buf == NULL) { - goto cleanup; + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; } - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL)) { + // Process the List of sign attributes + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + sign_id = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + sign_id = sign_place_from_dict( + NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { EMSG(_(e_dictreq)); - goto cleanup; - } - - // Line number where the sign is to be placed - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { - // Sign priority - prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } } - } - - if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) - == OK) { - rettv->vval.v_number = sign_id; - } - -cleanup: - xfree(group); + tv_list_append_number(rettv->vval.v_list, sign_id); + }); } /// "sign_undefine()" function @@ -8889,6 +9027,14 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *name; + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Undefine multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + rettv->vval.v_number = -1; if (argvars[0].v_type == VAR_UNKNOWN) { @@ -8911,11 +9057,7 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "sign_unplace()" function static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *dict; - dictitem_T *di; - int sign_id = 0; - buf_T *buf = NULL; - char_u *group = NULL; + dict_T *dict = NULL; rettv->vval.v_number = -1; @@ -8924,46 +9066,38 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - const char *group_chk = tv_get_string(&argvars[0]); - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[1].v_type != VAR_DICT) { EMSG(_(e_dictreq)); - goto cleanup; + return; } dict = argvars[1].vval.v_dict; - - if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { - buf = get_buf_arg(&di->di_tv); - if (buf == NULL) { - goto cleanup; - } - } - if (tv_dict_find(dict, "id", -1) != NULL) { - sign_id = tv_dict_get_number(dict, "id"); - } } - if (buf == NULL) { - // Delete the sign in all the buffers - FOR_ALL_BUFFERS(cbuf) { - if (sign_unplace(sign_id, group, cbuf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - } else { - if (sign_unplace(sign_id, group, buf, 0) == OK) { - rettv->vval.v_number = 0; - } + rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); +} + +/// "sign_unplacelist()" function +static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int retval; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; } -cleanup: - xfree(group); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + EMSG(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, retval); + }); } /* @@ -9969,6 +10103,38 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } +// "strptime({format}, {timestring})" function +static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char fmt_buf[NUMBUFLEN]; + char str_buf[NUMBUFLEN]; + + struct tm tmval = { + .tm_isdst = -1, + }; + char *fmt = (char *)tv_get_string_buf(&argvars[0], fmt_buf); + char *str = (char *)tv_get_string_buf(&argvars[1], str_buf); + + vimconv_T conv = { + .vc_type = CONV_NONE, + }; + char_u *enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + } + if (fmt == NULL + || os_strptime(str, fmt, &tmval) == NULL + || (rettv->vval.v_number = mktime(&tmval)) == -1) { + rettv->vval.v_number = 0; + } + if (conv.vc_type != CONV_NONE) { + xfree(fmt); + } + convert_setup(&conv, NULL, NULL); + xfree(enc); +} + /* * "strridx()" function */ @@ -10518,6 +10684,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,18 +10703,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, "xterm-256color"); + + 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, - &rettv->vval.v_number); + env, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; } @@ -10576,7 +10760,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); - channel_terminal_open(chan); + channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); } @@ -11138,17 +11322,23 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int winnr = 1; garray_T ga; char_u buf[50]; ga_init(&ga, (int)sizeof(char), 70); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); - ga_concat(&ga, buf); - sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); - ga_concat(&ga, buf); - ++winnr; + + // Do this twice to handle some window layouts properly. + for (int i = 0; i < 2; i++) { + int winnr = 1; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr, + wp->w_height); + ga_concat(&ga, buf); + snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr, + wp->w_width); + ga_concat(&ga, buf); + winnr++; + } } ga_append(&ga, NUL); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 02d32a4f86..71e4edc667 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_next = gc_first_list; gc_first_list = list; list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); + list->lua_table_ref = LUA_NOREF; return list; } @@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l) } list_log(l, NULL, NULL, "freelist"); - nlua_free_typval_list(l); + NLUA_CLEAR_REF(l->lua_table_ref); xfree(l); } @@ -1109,6 +1110,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern, watcher->key_pattern_len = key_pattern_len; watcher->callback = callback; watcher->busy = false; + watcher->needs_free = false; QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node); } @@ -1182,22 +1184,30 @@ bool tv_dict_watcher_remove(dict_T *const dict, const char *const key_pattern, QUEUE *w = NULL; DictWatcher *watcher = NULL; bool matched = false; - QUEUE_FOREACH(w, &dict->watchers) { + bool queue_is_busy = false; + QUEUE_FOREACH(w, &dict->watchers, { watcher = tv_dict_watcher_node_data(w); + if (watcher->busy) { + queue_is_busy = true; + } if (tv_callback_equal(&watcher->callback, &callback) && watcher->key_pattern_len == key_pattern_len && memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) { matched = true; break; } - } + }) if (!matched) { return false; } - QUEUE_REMOVE(w); - tv_dict_watcher_free(watcher); + if (queue_is_busy) { + watcher->needs_free = true; + } else { + QUEUE_REMOVE(w); + tv_dict_watcher_free(watcher); + } return true; } @@ -1258,9 +1268,10 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T rettv; + bool any_needs_free = false; dict->dv_refcount++; QUEUE *w; - QUEUE_FOREACH(w, &dict->watchers) { + QUEUE_FOREACH(w, &dict->watchers, { DictWatcher *watcher = tv_dict_watcher_node_data(w); if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) { rettv = TV_INITIAL_VALUE; @@ -1268,7 +1279,19 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, callback_call(&watcher->callback, 3, argv, &rettv); watcher->busy = false; tv_clear(&rettv); + if (watcher->needs_free) { + any_needs_free = true; + } } + }) + if (any_needs_free) { + QUEUE_FOREACH(w, &dict->watchers, { + DictWatcher *watcher = tv_dict_watcher_node_data(w); + if (watcher->needs_free) { + QUEUE_REMOVE(w); + tv_dict_watcher_free(watcher); + } + }) } tv_dict_unref(dict); @@ -1382,6 +1405,8 @@ dict_T *tv_dict_alloc(void) d->dv_copyID = 0; QUEUE_INIT(&d->watchers); + d->lua_table_ref = LUA_NOREF; + return d; } @@ -1432,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d) d->dv_used_next->dv_used_prev = d->dv_used_prev; } - nlua_free_typval_dict(d); + NLUA_CLEAR_REF(d->lua_table_ref); xfree(d); } @@ -1523,6 +1548,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 +2546,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 +2718,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, } } - assert(false); + abort(); return false; } @@ -2719,7 +2771,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2764,7 +2816,7 @@ bool tv_check_num(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2809,7 +2861,7 @@ bool tv_check_str(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index d8ede1e3ba..2b4612016b 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -89,6 +89,7 @@ typedef struct dict_watcher { size_t key_pattern_len; QUEUE node; bool busy; // prevent recursion if the dict is changed in the callback + bool needs_free; } DictWatcher; /// Bool variable values @@ -315,6 +316,7 @@ struct ufunc { int uf_calls; ///< nr of active calls bool uf_cleared; ///< func_clear() was already called garray_T uf_args; ///< arguments + garray_T uf_def_args; ///< default argument expressions garray_T uf_lines; ///< function lines int uf_profiling; ///< true when func is being profiled int uf_prof_initialized; @@ -340,8 +342,9 @@ struct ufunc { ///< used for s: variables int uf_refcount; ///< reference count, see func_name_refcount() funccall_T *uf_scoped; ///< l: local variables for closure - char_u uf_name[]; ///< Name of function; can start with <SNR>123_ - ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) + char_u uf_name[]; ///< Name of function (actual size equals name); + ///< can start with <SNR>123_ + ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; struct partial_S { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 70c998ef39..00260bc3f7 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -67,7 +67,7 @@ void func_init(void) /// Get function arguments. static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, bool skip) + int *varargs, garray_T *default_args, bool skip) { bool mustend = false; char_u *arg = *argp; @@ -78,12 +78,16 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, if (newargs != NULL) { ga_init(newargs, (int)sizeof(char_u *), 3); } + if (default_args != NULL) { + ga_init(default_args, (int)sizeof(char_u *), 3); + } if (varargs != NULL) { *varargs = false; } // Isolate the arguments: "arg1, arg2, ...)" + bool any_default = false; while (*p != endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { @@ -123,6 +127,38 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, *p = c; } + if (*skipwhite(p) == '=' && default_args != NULL) { + typval_T rettv; + + any_default = true; + p = skipwhite(p) + 1; + p = skipwhite(p); + char_u *expr = p; + if (eval1(&p, &rettv, false) != FAIL) { + ga_grow(default_args, 1); + + // trim trailing whitespace + while (p > expr && ascii_iswhite(p[-1])) { + p--; + } + c = *p; + *p = NUL; + expr = vim_strsave(expr); + if (expr == NULL) { + *p = c; + goto err_ret; + } + ((char_u **)(default_args->ga_data)) + [default_args->ga_len] = expr; + default_args->ga_len++; + *p = c; + } else { + mustend = true; + } + } else if (any_default) { + EMSG(_("E989: Non-default argument follows default argument")); + mustend = true; + } if (*p == ',') { p++; } else { @@ -149,6 +185,9 @@ err_ret: if (newargs != NULL) { ga_clear_strings(newargs); } + if (default_args != NULL) { + ga_clear_strings(default_args); + } return FAIL; } @@ -195,7 +234,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, true); + ret = get_function_args(&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -207,7 +246,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) pnewargs = NULL; } *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, &varargs, false); + ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, false); if (ret == FAIL || **arg != '>') { goto errret; } @@ -259,6 +298,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; + ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1); fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { flags |= FC_CLOSURE; @@ -715,6 +755,7 @@ static bool func_remove(ufunc_T *fp) static void func_clear_items(ufunc_T *fp) { ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); if (fp->uf_cb_free != NULL) { @@ -792,6 +833,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, bool islambda = false; char_u numbuf[NUMBUFLEN]; char_u *name; + typval_T *tv_to_free[MAX_FUNC_ARGS]; + int tv_to_free_len = 0; proftime_T wait_start; proftime_T call_start; int started_profiling = false; @@ -857,12 +900,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } // Init a: variables, unless none found (in lambda). - // Set a:0 to "argcount". + // Set a:0 to "argcount" less number of named arguments, if >= 0. // Set a:000 to a list with room for the "..." arguments. init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); if ((fp->uf_flags & FC_NOARGS) == 0) { add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", - (varnumber_T)(argcount - fp->uf_args.ga_len)); + (varnumber_T)(argcount >= fp->uf_args.ga_len + ? argcount - fp->uf_args.ga_len : 0)); } fc->l_avars.dv_lock = VAR_FIXED; if ((fp->uf_flags & FC_NOARGS) == 0) { @@ -892,8 +936,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "lastline", (varnumber_T)lastline); } - for (int i = 0; i < argcount; i++) { + bool default_arg_err = false; + for (int i = 0; i < argcount || i < fp->uf_args.ga_len; i++) { bool addlocal = false; + bool isdefault = false; + typval_T def_rettv; ai = i - fp->uf_args.ga_len; if (ai < 0) { @@ -902,6 +949,21 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (islambda) { addlocal = true; } + + // evaluate named argument default expression + isdefault = ai + fp->uf_def_args.ga_len >= 0 && i >= argcount; + if (isdefault) { + char_u *default_expr = NULL; + def_rettv.v_type = VAR_NUMBER; + def_rettv.vval.v_number = -1; + + default_expr = ((char_u **)(fp->uf_def_args.ga_data)) + [ai + fp->uf_def_args.ga_len]; + if (eval1(&default_expr, &def_rettv, true) == FAIL) { + default_arg_err = true; + break; + } + } } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). @@ -922,9 +984,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. - v->di_tv = argvars[i]; + v->di_tv = isdefault ? def_rettv : argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (isdefault) { + // Need to free this later, no matter where it's stored. + tv_to_free[tv_to_free_len++] = &v->di_tv; + } + if (addlocal) { // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. @@ -1046,7 +1113,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, save_did_emsg = did_emsg; did_emsg = FALSE; - if (islambda) { + if (default_arg_err && (fp->uf_flags & FC_ABORT)) { + did_emsg = true; + } else if (islambda) { char_u *p = *(char_u **)fp->uf_lines.ga_data + 7; // A Lambda always has the command "return {expr}". It is much faster @@ -1147,7 +1216,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, did_emsg |= save_did_emsg; depth--; - + for (int i = 0; i < tv_to_free_len; i++) { + tv_clear(tv_to_free[i]); + } cleanup_function_call(fc); if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -1490,7 +1561,7 @@ call_func( if (fp->uf_flags & FC_RANGE) { *doesrange = true; } - if (argcount < fp->uf_args.ga_len) { + if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { error = ERROR_TOOFEW; } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { error = ERROR_TOOMANY; @@ -1573,6 +1644,11 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) msg_puts(", "); } msg_puts((const char *)FUNCARG(fp, j)); + if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { + msg_puts(" = "); + msg_puts(((char **)(fp->uf_def_args.ga_data)) + [j - fp->uf_args.ga_len + fp->uf_def_args.ga_len]); + } } if (fp->uf_varargs) { if (j) { @@ -1836,6 +1912,7 @@ void ex_function(exarg_T *eap) char_u *arg; char_u *line_arg = NULL; garray_T newargs; + garray_T default_args; garray_T newlines; int varargs = false; int flags = 0; @@ -2039,7 +2116,8 @@ void ex_function(exarg_T *eap) } } - if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + if (get_function_args(&p, ')', &newargs, &varargs, + &default_args, eap->skip) == FAIL) { goto errret_2; } @@ -2458,6 +2536,7 @@ void ex_function(exarg_T *eap) fp->uf_refcount = 1; } fp->uf_args = newargs; + fp->uf_def_args = default_args; fp->uf_lines = newlines; if ((flags & FC_CLOSURE) != 0) { register_closure(fp); @@ -2480,6 +2559,7 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); + ga_clear_strings(&default_args); errret_2: ga_clear_strings(&newlines); ret_free: diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 13517d3df1..c02f730431 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 @@ -77,7 +82,10 @@ 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)); + ELOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, 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/multiqueue.c b/src/nvim/event/multiqueue.c index c9aa3acc4d..1e6d62135c 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -119,8 +119,8 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb, void multiqueue_free(MultiQueue *this) { assert(this); - while (!QUEUE_EMPTY(&this->headtail)) { - QUEUE *q = QUEUE_HEAD(&this->headtail); + QUEUE *q; + QUEUE_FOREACH(q, &this->headtail, { MultiQueueItem *item = multiqueue_node_data(q); if (this->parent) { QUEUE_REMOVE(&item->data.item.parent_item->node); @@ -128,7 +128,7 @@ void multiqueue_free(MultiQueue *this) } QUEUE_REMOVE(q); xfree(item); - } + }) xfree(this); } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 8e9964bd37..b93d6cc0ab 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -270,9 +270,6 @@ static void process_close_event(void **argv) { Process *proc = argv[0]; shell_free_argv(proc->argv); - if (proc->type == kProcessTypePty) { - xfree(((PtyProcess *)proc)->term_name); - } if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start(). proc->cb(proc, proc->status, proc->data); } 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 a2487336f1..3e330b88a2 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -712,14 +712,15 @@ void ex_retab(exarg_T *eap) long len; long col; long vcol; - long start_col = 0; /* For start of white-space string */ - long start_vcol = 0; /* For start of white-space string */ - int temp; + long start_col = 0; // For start of white-space string + long start_vcol = 0; // For start of white-space string long old_len; char_u *ptr; - char_u *new_line = (char_u *)1; /* init to non-NULL */ - int did_undo; /* called u_save for current line */ - int new_ts; + char_u *new_line = (char_u *)1; // init to non-NULL + int did_undo; // called u_save for current line + long *new_vts_array = NULL; + char_u *new_ts_str; // string value of tab argument + int save_list; linenr_T first_line = 0; /* first changed line */ linenr_T last_line = 0; /* last changed line */ @@ -727,14 +728,24 @@ void ex_retab(exarg_T *eap) save_list = curwin->w_p_list; curwin->w_p_list = 0; /* don't want list mode here */ - new_ts = getdigits_int(&(eap->arg), false, -1); - if (new_ts < 0) { - EMSG(_(e_positive)); + new_ts_str = eap->arg; + if (!tabstop_set(eap->arg, &new_vts_array)) { return; } - if (new_ts == 0) - new_ts = curbuf->b_p_ts; - for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) { + while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { + (eap->arg)++; + } + + // This ensures that either new_vts_array and new_ts_str are freshly + // allocated, or new_vts_array points to an existing array and new_ts_str + // is null. + if (new_vts_array == NULL) { + new_vts_array = curbuf->b_p_vts_array; + new_ts_str = NULL; + } else { + new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str); + } + for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { ptr = ml_get(lnum); col = 0; vcol = 0; @@ -758,13 +769,12 @@ void ex_retab(exarg_T *eap) len = num_spaces = vcol - start_vcol; num_tabs = 0; if (!curbuf->b_p_et) { - temp = new_ts - (start_vcol % new_ts); - if (num_spaces >= temp) { - num_spaces -= temp; - num_tabs++; - } - num_tabs += num_spaces / new_ts; - num_spaces -= (num_spaces / new_ts) * new_ts; + int t, s; + + tabstop_fromto(start_vcol, vcol, + curbuf->b_p_ts, new_vts_array, &t, &s); + num_tabs = t; + num_spaces = s; } if (curbuf->b_p_et || got_tab || (num_spaces + num_tabs < len)) { @@ -780,7 +790,8 @@ void ex_retab(exarg_T *eap) /* len is actual number of white characters used */ len = num_spaces + num_tabs; old_len = (long)STRLEN(ptr); - new_line = xmalloc(old_len - col + start_col + len + 1); + long new_len = old_len - col + start_col + len + 1; + new_line = xmalloc(new_len); if (start_col > 0) memmove(new_line, ptr, (size_t)start_col); @@ -790,7 +801,12 @@ 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; + extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, + (colnr_T)new_len - 1, kExtmarkUndo); + } if (first_line == 0) { first_line = lnum; } @@ -814,15 +830,42 @@ void ex_retab(exarg_T *eap) if (got_int) EMSG(_(e_interr)); - if (curbuf->b_p_ts != new_ts) + // If a single value was given then it can be considered equal to + // either the value of 'tabstop' or the value of 'vartabstop'. + if (tabstop_count(curbuf->b_p_vts_array) == 0 + && tabstop_count(new_vts_array) == 1 + && curbuf->b_p_ts == tabstop_first(new_vts_array)) { + // not changed + } else if (tabstop_count(curbuf->b_p_vts_array) > 0 + && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { + // not changed + } else { redraw_curbuf_later(NOT_VALID); + } if (first_line != 0) { changed_lines(first_line, 0, last_line + 1, 0L, true); } curwin->w_p_list = save_list; /* restore 'list' */ - curbuf->b_p_ts = new_ts; + if (new_ts_str != NULL) { // set the new tabstop + // If 'vartabstop' is in use or if the value given to retab has more + // than one tabstop then update 'vartabstop'. + long *old_vts_ary = curbuf->b_p_vts_array; + + if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { + set_string_option_direct("vts", -1, new_ts_str, + OPT_FREE | OPT_LOCAL, 0); + curbuf->b_p_vts_array = new_vts_array; + xfree(old_vts_ary); + } else { + // 'vartabstop' wasn't in use and a single value was given to + // retab then update 'tabstop'. + curbuf->b_p_ts = tabstop_first(new_vts_array); + xfree(new_vts_array); + } + xfree(new_ts_str); + } coladvance(curwin->w_curswant); u_clearline(); @@ -925,12 +968,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, kExtmarkNOOP); - // extmarks are handled separately - extmark_move_region(curbuf, line1-1, 0, start_byte, - line2-line1+1, 0, extent_byte, - dest+line_off, 0, dest_byte+byte_off, - kExtmarkUndo); - changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -952,6 +989,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) smsg(_("%" PRId64 " lines moved"), (int64_t)num_lines); } + extmark_move_region(curbuf, line1-1, 0, start_byte, + line2-line1+1, 0, extent_byte, + dest+line_off, 0, dest_byte+byte_off, + kExtmarkUndo); + /* * Leave the cursor on the last of the moved lines. */ @@ -1404,19 +1446,20 @@ do_shell( * For autocommands we want to get the output on the current screen, to * avoid having to type return below. */ - msg_putchar('\r'); /* put cursor at start of line */ - msg_putchar('\n'); /* may shift screen one line up */ + msg_putchar('\r'); // put cursor at start of line + msg_putchar('\n'); // may shift screen one line up - /* warning message before calling the shell */ + // warning message before calling the shell if (p_warn && !autocmd_busy - && msg_silent == 0) + && msg_silent == 0) { FOR_ALL_BUFFERS(buf) { if (bufIsChanged(buf)) { MSG_PUTS(_("[No write since last change]\n")); break; } } + } // This ui_cursor_goto is required for when the '\n' resulted in a "delete line // 1" command to the terminal. @@ -2178,6 +2221,8 @@ theend: /// ECMD_OLDBUF: use existing buffer if it exists /// ECMD_FORCEIT: ! used for Ex command /// ECMD_ADDBUF: don't edit, just add to buffer list +/// ECMD_ALTBUF: like ECMD_ADDBUF and also set the alternate +/// file /// @param oldwin Should be "curwin" when editing a new buffer in the current /// window, NULL when splitting the window first. When not NULL /// info of the previous buffer for "oldwin" is stored. @@ -2234,8 +2279,10 @@ int do_ecmd( path_fix_case(sfname); // set correct case for sfname #endif - if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL)) + if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) + && (ffname == NULL || *ffname == NUL)) { goto theend; + } if (ffname == NULL) other_file = TRUE; @@ -2265,15 +2312,16 @@ int do_ecmd( // If the file was changed we may not be allowed to abandon it: // - if we are going to re-edit the same file // - or if we are the only window on this file and if ECMD_HIDE is FALSE - if ( ((!other_file && !(flags & ECMD_OLDBUF)) - || (curbuf->b_nwindows == 1 - && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) - && check_changed(curbuf, (p_awa ? CCGD_AW : 0) - | (other_file ? 0 : CCGD_MULTWIN) - | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) - | (eap == NULL ? 0 : CCGD_EXCMD))) { - if (fnum == 0 && other_file && ffname != NULL) + if (((!other_file && !(flags & ECMD_OLDBUF)) + || (curbuf->b_nwindows == 1 + && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF)))) + && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | (other_file ? 0 : CCGD_MULTWIN) + | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) + | (eap == NULL ? 0 : CCGD_EXCMD))) { + if (fnum == 0 && other_file && ffname != NULL) { (void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum); + } goto theend; } @@ -2303,25 +2351,35 @@ int do_ecmd( * Otherwise we re-use the current buffer. */ if (other_file) { - if (!(flags & ECMD_ADDBUF)) { - if (!cmdmod.keepalt) + if (!(flags & (ECMD_ADDBUF | ECMD_ALTBUF))) { + if (!cmdmod.keepalt) { curwin->w_alt_fnum = curbuf->b_fnum; - if (oldwin != NULL) + } + if (oldwin != NULL) { buflist_altfpos(oldwin); + } } if (fnum) { buf = buflist_findnr(fnum); } else { - if (flags & ECMD_ADDBUF) { - linenr_T tlnum = 1L; + if (flags & (ECMD_ADDBUF | ECMD_ALTBUF)) { + // Default the line number to zero to avoid that a wininfo item + // is added for the current window. + linenr_T tlnum = 0; if (command != NULL) { tlnum = atol((char *)command); if (tlnum <= 0) tlnum = 1L; } - (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED); + // Add BLN_NOCURWIN to avoid a new wininfo items are associated + // with the current window. + const buf_T *const newbuf + = buflist_new(ffname, sfname, tlnum, BLN_LISTED | BLN_NOCURWIN); + if (newbuf != NULL && (flags & ECMD_ALTBUF)) { + curwin->w_alt_fnum = newbuf->b_fnum; + } goto theend; } buf = buflist_new(ffname, sfname, 0L, @@ -2413,7 +2471,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 @@ -2464,8 +2525,7 @@ int do_ecmd( curwin->w_pcmark.lnum = 1; curwin->w_pcmark.col = 0; } else { // !other_file - if ((flags & ECMD_ADDBUF) - || check_fname() == FAIL) { + if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) || check_fname() == FAIL) { goto theend; } oldbuf = (flags & ECMD_OLDBUF); @@ -2531,13 +2591,13 @@ int do_ecmd( goto theend; } u_unchanged(curbuf); - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, BFA_KEEP_UNDO); // Tell readfile() not to clear or reload undo info. readfile_flags = READ_KEEP_UNDO; } else { - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, false); buf_freeall(curbuf, 0); // Free all things for buffer. } // If autocommands deleted the buffer we were going to re-edit, give @@ -3126,6 +3186,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 +3370,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; @@ -3988,6 +4047,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); @@ -4174,7 +4238,7 @@ skip: // when interactive leave cursor on the match if (!subflags.do_ask) { if (endcolumn) { - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else { beginline(BL_WHITE | BL_FIX); } @@ -4217,7 +4281,7 @@ skip: size_t subsize = preview_lines.subresults.size; if (preview && !aborting()) { if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable. - set_string_option_direct((char_u *)"icm", -1, (char_u *)"", OPT_FREE, + set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE, SID_NONE); } else if (*p_icm != NUL && pat != NULL) { if (pre_src_id == 0) { @@ -4518,7 +4582,7 @@ prepare_tagpreview ( RESET_BINDING(curwin); /* don't take over 'scrollbind' and 'cursorbind' */ curwin->w_p_diff = false; // no 'diff' - set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn' + set_string_option_direct("fdc", -1, // no 'foldcolumn' (char_u *)"0", OPT_FREE, SID_NONE); return true; } @@ -4996,7 +5060,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, if (keep_lang) { flags |= TAG_KEEP_LANG; } - if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK + if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK && *num_matches > 0) { /* Sort the matches found on the heuristic number that is after the * tag name. */ @@ -5013,7 +5077,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, static void prepare_help_buffer(void) { curbuf->b_help = true; - set_string_option_direct((char_u *)"buftype", -1, (char_u *)"help", + set_string_option_direct("buftype", -1, (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); // Always set these options after jumping to a help tag, because the @@ -5023,13 +5087,13 @@ static void prepare_help_buffer(void) // Only set it when needed, buf_init_chartab() is some work. char_u *p = (char_u *)"!-~,^*,^|,^\",192-255"; if (STRCMP(curbuf->b_p_isk, p) != 0) { - set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0); + set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); check_buf_options(curbuf); (void)buf_init_chartab(curbuf, FALSE); } // Don't use the global foldmethod. - set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual", + set_string_option_direct("fdm", -1, (char_u *)"manual", OPT_FREE|OPT_LOCAL, 0); curbuf->b_p_ts = 8; // 'tabstop' is 8. @@ -5649,7 +5713,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, cmdmod.tab = 0; // disable :tab modifier cmdmod.noswapfile = true; // disable swap for preview buffer // disable file info message - set_string_option_direct((char_u *)"shm", -1, (char_u *)"F", OPT_FREE, + set_string_option_direct("shm", -1, (char_u *)"F", OPT_FREE, SID_NONE); bool outside_curline = (eap->line1 != old_cusr.lnum @@ -5772,7 +5836,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, update_screen(SOME_VALID); RedrawingDisabled = save_rd; - set_string_option_direct((char_u *)"shm", -1, save_shm_p, OPT_FREE, SID_NONE); + set_string_option_direct("shm", -1, save_shm_p, OPT_FREE, SID_NONE); xfree(save_shm_p); cmdmod = save_cmdmod; diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index b564cde56c..1b54b3a898 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -16,7 +16,7 @@ #define ECMD_OLDBUF 0x04 // use existing buffer if it exists #define ECMD_FORCEIT 0x08 // ! used in Ex command #define ECMD_ADDBUF 0x10 // don't edit, just add to buffer list - +#define ECMD_ALTBUF 0x20 // like ECMD_ADDBUF and set the alternate file /* for lnum argument in do_ecmd() */ #define ECMD_LASTL (linenr_T)0 /* use last position in loaded file */ diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index e9046da800..d99383303b 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -176,6 +176,12 @@ module.cmds = { func='ex_edit', }, { + command='balt', + flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN), + addr_type='ADDR_NONE', + func='ex_edit', + }, + { command='bdelete', flags=bit.bor(BANG, RANGE, BUFNAME, COUNT, EXTRA, TRLBAR), addr_type='ADDR_BUFFERS', @@ -922,6 +928,12 @@ module.cmds = { func='ex_edit', }, { + command='eval', + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + addr_type='ADDR_NONE', + func='ex_eval', + }, + { command='ex', flags=bit.bor(BANG, FILE1, CMDARG, ARGOPT, TRLBAR), addr_type='ADDR_NONE', @@ -2515,8 +2527,8 @@ module.cmds = { }, { command='source', - flags=bit.bor(BANG, FILE1, TRLBAR, SBOXOK, CMDWIN), - addr_type='ADDR_NONE', + flags=bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN), + addr_type='ADDR_LINES', func='ex_source', }, { diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c400975108..950a1a436f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -29,6 +29,7 @@ #include "nvim/getchar.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/memline.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/garray.h" @@ -119,6 +120,9 @@ struct source_cookie { /// batch mode debugging: don't save and restore typeahead. static bool debug_greedy = false; +static char *debug_oldval = NULL; // old and newval for debug expressions +static char *debug_newval = NULL; + /// Debug mode. Repeatedly get Ex commands, until told to continue normal /// execution. void do_debug(char_u *cmd) @@ -165,6 +169,16 @@ void do_debug(char_u *cmd) if (!debug_did_msg) { MSG(_("Entering Debug mode. Type \"cont\" to continue.")); } + if (debug_oldval != NULL) { + smsg(_("Oldval = \"%s\""), debug_oldval); + xfree(debug_oldval); + debug_oldval = NULL; + } + if (debug_newval != NULL) { + smsg(_("Newval = \"%s\""), debug_newval); + xfree(debug_newval); + debug_newval = NULL; + } if (sourcing_name != NULL) { msg(sourcing_name); } @@ -173,7 +187,6 @@ void do_debug(char_u *cmd) } else { smsg(_("cmd: %s"), cmd); } - // Repeat getting a command and executing it. for (;; ) { msg_scroll = true; @@ -513,11 +526,13 @@ bool dbg_check_skipped(exarg_T *eap) /// This is a grow-array of structs. struct debuggy { int dbg_nr; ///< breakpoint number - int dbg_type; ///< DBG_FUNC or DBG_FILE - char_u *dbg_name; ///< function or file name + int dbg_type; ///< DBG_FUNC or DBG_FILE or DBG_EXPR + char_u *dbg_name; ///< function, expression or file name regprog_T *dbg_prog; ///< regexp program linenr_T dbg_lnum; ///< line number in function or file int dbg_forceit; ///< ! used + typval_T *dbg_val; ///< last result of watchexpression + int dbg_level; ///< stored nested level for expr }; static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL }; @@ -529,6 +544,7 @@ static int last_breakp = 0; // nr of last defined breakpoint static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL }; #define DBG_FUNC 1 #define DBG_FILE 2 +#define DBG_EXPR 3 /// Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them @@ -561,6 +577,8 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) } bp->dbg_type = DBG_FILE; here = true; + } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) { + bp->dbg_type = DBG_EXPR; } else { EMSG2(_(e_invarg2), p); return FAIL; @@ -589,6 +607,9 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) bp->dbg_name = vim_strsave(p); } else if (here) { bp->dbg_name = vim_strsave(curbuf->b_ffname); + } else if (bp->dbg_type == DBG_EXPR) { + bp->dbg_name = vim_strsave(p); + bp->dbg_val = eval_expr(bp->dbg_name); } else { // Expand the file name in the same way as do_source(). This means // doing it twice, so that $DIR/file gets expanded when $DIR is @@ -620,7 +641,6 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) void ex_breakadd(exarg_T *eap) { struct debuggy *bp; - char_u *pat; garray_T *gap; gap = &dbg_breakp; @@ -632,22 +652,28 @@ void ex_breakadd(exarg_T *eap) bp = &DEBUGGY(gap, gap->ga_len); bp->dbg_forceit = eap->forceit; - pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); - if (pat != NULL) { - bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); - xfree(pat); - } - if (pat == NULL || bp->dbg_prog == NULL) { - xfree(bp->dbg_name); - } else { - if (bp->dbg_lnum == 0) { // default line number is 1 - bp->dbg_lnum = 1; + if (bp->dbg_type != DBG_EXPR) { + char_u *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); + if (pat != NULL) { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + xfree(pat); } - if (eap->cmdidx != CMD_profile) { - DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; - debug_tick++; + if (pat == NULL || bp->dbg_prog == NULL) { + xfree(bp->dbg_name); + } else { + if (bp->dbg_lnum == 0) { // default line number is 1 + bp->dbg_lnum = 1; + } + if (eap->cmdidx != CMD_profile) { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + debug_tick++; + } + gap->ga_len++; } - gap->ga_len++; + } else { + // DBG_EXPR + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + debug_tick++; } } } @@ -690,7 +716,7 @@ void ex_breakdel(exarg_T *eap) todel = 0; del_all = true; } else { - // ":breakdel {func|file} [lnum] {name}" + // ":breakdel {func|file|expr} [lnum] {name}" if (dbg_parsearg(eap->arg, gap) == FAIL) { return; } @@ -715,6 +741,10 @@ void ex_breakdel(exarg_T *eap) } else { while (!GA_EMPTY(gap)) { xfree(DEBUGGY(gap, todel).dbg_name); + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) { + tv_free(DEBUGGY(gap, todel).dbg_val); + } vim_regfree(DEBUGGY(gap, todel).dbg_prog); gap->ga_len--; if (todel < gap->ga_len) { @@ -749,11 +779,15 @@ void ex_breaklist(exarg_T *eap) if (bp->dbg_type == DBG_FILE) { home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true); } - smsg(_("%3d %s %s line %" PRId64), - bp->dbg_nr, - bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, - (int64_t)bp->dbg_lnum); + if (bp->dbg_type != DBG_EXPR) { + smsg(_("%3d %s %s line %" PRId64), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (int64_t)bp->dbg_lnum); + } else { + smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); + } } } } @@ -813,6 +847,7 @@ debuggy_find( // an already found breakpoint. bp = &DEBUGGY(gap, i); if ((bp->dbg_type == DBG_FILE) == file + && bp->dbg_type != DBG_EXPR && (gap == &prof_ga || (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum)))) { // Save the value of got_int and reset it. We don't want a @@ -827,6 +862,46 @@ debuggy_find( } } got_int |= prev_got_int; + } else if (bp->dbg_type == DBG_EXPR) { + bool line = false; + + prev_got_int = got_int; + got_int = false; + + typval_T *tv = eval_expr(bp->dbg_name); + if (tv != NULL) { + if (bp->dbg_val == NULL) { + debug_oldval = typval_tostring(NULL); + bp->dbg_val = tv; + debug_newval = typval_tostring(bp->dbg_val); + line = true; + } else { + if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK + && tv->vval.v_number == false) { + line = true; + debug_oldval = typval_tostring(bp->dbg_val); + // Need to evaluate again, typval_compare() overwrites "tv". + typval_T *v = eval_expr(bp->dbg_name); + debug_newval = typval_tostring(v); + tv_free(bp->dbg_val); + bp->dbg_val = v; + } + tv_free(tv); + } + } else if (bp->dbg_val != NULL) { + debug_oldval = typval_tostring(bp->dbg_val); + debug_newval = typval_tostring(NULL); + tv_free(bp->dbg_val); + bp->dbg_val = NULL; + line = true; + } + + if (line) { + lnum = after > 0 ? after : 1; + break; + } + + got_int |= prev_got_int; } } if (name != fname) { @@ -1639,10 +1714,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); @@ -1657,9 +1732,11 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) /// AL_DEL: remove files in 'str' from the argument list. /// @param after /// 0 means before first one +/// @param will_edit will edit added argument /// /// @return FAIL for failure, OK otherwise. -static int do_arglist(char_u *str, int what, int after) +static int do_arglist(char_u *str, int what, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL { garray_T new_ga; int exp_count; @@ -1733,10 +1810,11 @@ static int do_arglist(char_u *str, int what, int after) } if (what == AL_ADD) { - (void)alist_add_list(exp_count, exp_files, after); + alist_add_list(exp_count, exp_files, after, will_edit); xfree(exp_files); - } else { // what == AL_SET - alist_set(ALIST(curwin), exp_count, exp_files, false, NULL, 0); + } else { + assert(what == AL_SET); + alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); } } @@ -1956,7 +2034,7 @@ void ex_next(exarg_T *eap) | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) { if (*eap->arg != NUL) { // redefine file list - if (do_arglist(eap->arg, AL_SET, 0) == FAIL) { + if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { return; } i = 0; @@ -1974,7 +2052,7 @@ void ex_argedit(exarg_T *eap) // Whether curbuf will be reused, curbuf->b_ffname will be set. bool curbuf_is_reusable = curbuf_reusable(); - if (do_arglist(eap->arg, AL_ADD, i) == FAIL) { + if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { return; } maketitle(); @@ -1994,7 +2072,8 @@ void ex_argedit(exarg_T *eap) void ex_argadd(exarg_T *eap) { do_arglist(eap->arg, AL_ADD, - eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1); + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, + false); maketitle(); } @@ -2041,7 +2120,7 @@ void ex_argdelete(exarg_T *eap) } } } else { - do_arglist(eap->arg, AL_DEL, 0); + do_arglist(eap->arg, AL_DEL, 0, false); } maketitle(); } @@ -2292,9 +2371,9 @@ void ex_listdo(exarg_T *eap) /// Files[] itself is not taken over. /// /// @param after: where to add: 0 = before first one -/// -/// @return index of first added argument -static int alist_add_list(int count, char_u **files, int after) +/// @param will_edit will edit adding argument +static void alist_add_list(int count, char_u **files, int after, bool will_edit) + FUNC_ATTR_NONNULL_ALL { int old_argcount = ARGCOUNT; ga_grow(&ALIST(curwin)->al_ga, count); @@ -2310,15 +2389,15 @@ static int alist_add_list(int count, char_u **files, int after) (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); } for (int i = 0; i < count; i++) { + const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); ARGLIST[after + i].ae_fname = files[i]; - ARGLIST[after + i].ae_fnum = buflist_add(files[i], - BLN_LISTED | BLN_CURBUF); + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); } ALIST(curwin)->al_ga.ga_len += count; if (old_argcount > 0 && curwin->w_arg_idx >= after) { curwin->w_arg_idx += count; } - return after; + return; } } @@ -2375,13 +2454,13 @@ void ex_compiler(exarg_T *eap) // Set "b:current_compiler" from "current_compiler". p = get_var_value("g:current_compiler"); if (p != NULL) { - set_internal_string_var((char_u *)"b:current_compiler", p); + set_internal_string_var("b:current_compiler", p); } // Restore "current_compiler" for ":compiler {name}". if (!eap->forceit) { if (old_cur_comp != NULL) { - set_internal_string_var((char_u *)"g:current_compiler", + set_internal_string_var("g:current_compiler", old_cur_comp); xfree(old_cur_comp); } else { @@ -2522,7 +2601,7 @@ void ex_pyxdo(exarg_T *eap) } } -/// ":source {fname}" +/// ":source [{fname}]" void ex_source(exarg_T *eap) { cmd_source(eap->arg, eap); @@ -2530,8 +2609,8 @@ void ex_source(exarg_T *eap) static void cmd_source(char_u *fname, exarg_T *eap) { - if (*fname == NUL) { - EMSG(_(e_argreq)); + if (eap != NULL && *fname == NUL) { + cmd_source_buffer(eap); } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands // Need to execute the commands directly. This is required at least @@ -2549,6 +2628,38 @@ static void cmd_source(char_u *fname, exarg_T *eap) } } +typedef struct { + linenr_T curr_lnum; + const linenr_T final_lnum; +} GetBufferLineCookie; + +/// Get one line from the current selection in the buffer. +/// Called by do_cmdline() when it's called from cmd_source_buffer(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. +static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat) +{ + GetBufferLineCookie *p = cookie; + if (p->curr_lnum > p->final_lnum) { + return NULL; + } + char_u *curr_line = ml_get(p->curr_lnum); + p->curr_lnum++; + return (char_u *)xstrdup((const char *)curr_line); +} + +static void cmd_source_buffer(const exarg_T *eap) + FUNC_ATTR_NONNULL_ALL +{ + GetBufferLineCookie cookie = { + .curr_lnum = eap->line1, + .final_lnum = eap->line2, + }; + source_using_linegetter((void *)&cookie, get_buffer_line, + ":source (no file)"); +} + /// ":source" and associated commands. /// /// @return address holding the next breakpoint line for a source cookie @@ -2620,10 +2731,9 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)xstrdup(buf); } -/// Executes lines in `src` as Ex commands. -/// -/// @see do_source() -int do_source_str(const char *cmd, const char *traceback_name) +static int source_using_linegetter(void *cookie, + LineGetter fgetline, + const char *traceback_name) { char_u *save_sourcing_name = sourcing_name; linenr_T save_sourcing_lnum = sourcing_lnum; @@ -2638,22 +2748,33 @@ int do_source_str(const char *cmd, const char *traceback_name) } sourcing_lnum = 0; - GetStrLineCookie cookie = { - .buf = (char_u *)cmd, - .offset = 0, - }; const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; - int retval = do_cmdline(NULL, get_str_line, (void *)&cookie, + funccal_entry_T entry; + save_funccal(&entry); + int retval = do_cmdline(NULL, fgetline, cookie, DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); - current_sctx = save_current_sctx; sourcing_lnum = save_sourcing_lnum; sourcing_name = save_sourcing_name; + current_sctx = save_current_sctx; + restore_funccal(); return retval; } +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() +int do_source_str(const char *cmd, const char *traceback_name) +{ + GetStrLineCookie cookie = { + .buf = (char_u *)cmd, + .offset = 0, + }; + return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); +} + /// Reads the file `fname` and executes its lines as Ex commands. /// /// This function may be called recursively! @@ -2978,6 +3099,7 @@ void scriptnames_slash_adjust(void) # endif /// Get a pointer to a script name. Used for ":verbose set". +/// Message appended to "Last set from " char_u *get_scriptname(LastSet last_set, bool *should_free) { *should_free = false; @@ -2993,6 +3115,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) return (char_u *)_("environment variable"); case SID_ERROR: return (char_u *)_("error handler"); + case SID_WINLAYOUT: + return (char_u *)_("changed window size"); case SID_LUA: return (char_u *)_("Lua"); case SID_API_CLIENT: @@ -3766,7 +3890,7 @@ void ex_drop(exarg_T *eap) // and mostly only one file is dropped. // This also ignores wildcards, since it is very unlikely the user is // editing a file name with a wildcard character. - do_arglist(eap->arg, AL_SET, 0); + do_arglist(eap->arg, AL_SET, 0, false); // Expanding wildcards may result in an empty argument list. E.g. when // editing "foo.pyc" and ".pyc" is in 'wildignore'. Assume that we diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ca84d375ce..f928c61ea4 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -188,8 +188,8 @@ struct exarg { // used for completion on the command line struct expand { - int xp_context; // type of expansion char_u *xp_pattern; // start of item to expand + int xp_context; // type of expansion size_t xp_pattern_len; // bytes in xp_pattern before cursor char_u *xp_arg; // completion function sctx_T xp_script_ctx; // SCTX for completion function @@ -199,9 +199,9 @@ struct expand { // characters need to be escaped #endif int xp_numfiles; // number of files found by file name completion + int xp_col; // cursor position in line char_u **xp_files; // list of files char_u *xp_line; // text being completed - int xp_col; // cursor position in line }; // values for xp_backslash diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ccf7dd0f68..ae5c334592 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); @@ -1860,6 +1857,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_echoerr: case CMD_echomsg: case CMD_echon: + case CMD_eval: case CMD_execute: case CMD_filter: case CMD_help: @@ -2182,7 +2180,7 @@ int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only) // Set 'eventignore' to "all". Restore the // existing option value later. cmdmod.save_ei = vim_strsave(p_ei); - set_string_option_direct((char_u *)"ei", -1, + set_string_option_direct("ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); } continue; @@ -2294,9 +2292,8 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) } if (cmdmod.save_ei != NULL) { - /* Restore 'eventignore' to the value before ":noautocmd". */ - set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, - OPT_FREE, SID_NONE); + // Restore 'eventignore' to the value before ":noautocmd". + set_string_option_direct("ei", -1, cmdmod.save_ei, OPT_FREE, SID_NONE); free_string_option(cmdmod.save_ei); } @@ -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); @@ -3515,13 +3513,20 @@ const char * set_one_cmd_context( xp->xp_context = EXPAND_BUFFERS; xp->xp_pattern = (char_u *)arg; break; + case CMD_diffget: + case CMD_diffput: + // If current buffer is in diff mode, complete buffer names + // which are in diff mode, and different than current buffer. + xp->xp_context = EXPAND_DIFF_BUFFERS; + xp->xp_pattern = (char_u *)arg; + break; case CMD_USER: case CMD_USER_BUF: if (context != EXPAND_NOTHING) { // EX_XFILE: file names are handled above. if (!(ea.argt & EX_XFILE)) { if (context == EXPAND_MENUS) { - return (const char *)set_context_in_menu_cmd(xp, (char_u *)cmd, + return (const char *)set_context_in_menu_cmd(xp, cmd, (char_u *)arg, forceit); } else if (context == EXPAND_COMMANDS) { return arg; @@ -3601,7 +3606,7 @@ const char * set_one_cmd_context( case CMD_tmenu: case CMD_tunmenu: case CMD_popup: case CMD_emenu: return (const char *)set_context_in_menu_cmd( - xp, (char_u *)cmd, (char_u *)arg, forceit); + xp, cmd, (char_u *)arg, forceit); case CMD_colorscheme: xp->xp_context = EXPAND_COLORS; @@ -3680,6 +3685,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; } @@ -3920,7 +3929,7 @@ static linenr_T get_address(exarg_T *eap, } searchcmdlen = 0; flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; - if (!do_search(NULL, c, cmd, 1L, flags, NULL)) { + if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3968,7 +3977,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); } } @@ -5173,6 +5182,7 @@ static const char *command_complete[] = [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", + [EXPAND_DIFF_BUFFERS] = "diff_buffer", [EXPAND_DIRECTORIES] = "dir", [EXPAND_ENV_VARS] = "environment", [EXPAND_EVENTS] = "event", @@ -5187,6 +5197,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 +5411,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; @@ -6272,14 +6283,14 @@ int parse_compl_arg(const char_u *value, int vallen, int *complp, return OK; } -int cmdcomplete_str_to_type(char_u *complete_str) +int cmdcomplete_str_to_type(const char *complete_str) { for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { char *cmd_compl = get_command_complete(i); if (cmd_compl == NULL) { continue; } - if (STRCMP(complete_str, command_complete[i]) == 0) { + if (strcmp(complete_str, command_complete[i]) == 0) { return i; } } @@ -7269,9 +7280,7 @@ static void ex_find(exarg_T *eap) } } -/* - * ":edit", ":badd", ":visual". - */ +/// ":edit", ":badd", ":balt", ":visual". static void ex_edit(exarg_T *eap) { do_exedit(eap, NULL); @@ -7345,7 +7354,9 @@ do_exedit( else if (eap->cmdidx == CMD_enew) readonlymode = FALSE; /* 'readonly' doesn't make sense in an empty buffer */ - setpcmark(); + if (eap->cmdidx != CMD_balt && eap->cmdidx != CMD_badd) { + setpcmark(); + } if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), NULL, eap, eap->do_ecmd_lnum, (buf_hide(curbuf) ? ECMD_HIDE : 0) @@ -7353,6 +7364,7 @@ do_exedit( // After a split we can use an existing buffer. + (old_curwin != NULL ? ECMD_OLDBUF : 0) + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) + + (eap->cmdidx == CMD_balt ? ECMD_ALTBUF : 0) , old_curwin == NULL ? curwin : NULL) == FAIL) { // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { @@ -7483,7 +7495,7 @@ static void ex_syncbind(exarg_T *eap) ctrl_o[0] = Ctrl_O; ctrl_o[1] = 0; - ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE); + ins_typebuf(ctrl_o, REMAP_NONE, 0, true, false); } } } @@ -7503,8 +7515,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 +7600,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); @@ -7752,6 +7765,11 @@ static void do_exmap(exarg_T *eap, int isabbrev) static void ex_winsize(exarg_T *eap) { char_u *arg = eap->arg; + + if (!ascii_isdigit(*arg)) { + EMSG2(_(e_invarg2), arg); + return; + } int w = getdigits_int(&arg, false, 10); arg = skipwhite(arg); char_u *p = arg; @@ -8717,7 +8735,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) * Evaluate cmdline variables. * * change '%' to curbuf->b_ffname - * '#' to curwin->w_altfile + * '#' to curwin->w_alt_fnum * '<cword>' to word under the cursor * '<cWORD>' to WORD under the cursor * '<cexpr>' to C-expression under the cursor diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 0917c6dd02..5ca88002f1 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -788,6 +788,15 @@ void report_discard_pending(int pending, void *value) } } +// ":eval". +void ex_eval(exarg_T *eap) +{ + typval_T tv; + + if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) { + tv_clear(&tv); + } +} /* * ":if". diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2aa66f6a8c..7159b27665 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" @@ -274,8 +275,9 @@ static void init_incsearch_state(incsearch_state_T *s) // Return true when 'incsearch' highlighting is to be done. // Sets search_first_line and search_last_line to the address range. -static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, - int *skiplen, int *patlen) +static bool do_incsearch_highlighting(int firstc, int *search_delim, + incsearch_state_T *s, int *skiplen, + int *patlen) FUNC_ATTR_NONNULL_ALL { char_u *cmd; @@ -302,6 +304,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, search_last_line = MAXLNUM; if (firstc == '/' || firstc == '?') { + *search_delim = firstc; return true; } if (firstc != ':') { @@ -370,6 +373,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, p = skipwhite(p); delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; + *search_delim = delim; end = skip_regexp(p, delim, p_magic, NULL); use_last_pat = end == p && *end == delim; @@ -430,12 +434,14 @@ static void may_do_incsearch_highlighting(int firstc, long count, int skiplen, patlen; char_u next_char; char_u use_last_pat; + int search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); finish_incsearch_highlighting(false, s, true); return; @@ -489,7 +495,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, ccline.cmdbuff[skiplen + patlen] = NUL; memset(&sia, 0, sizeof(sia)); sia.sa_tm = &tm; - found = do_search(NULL, firstc == ':' ? '/' : firstc, + found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, ccline.cmdbuff + skiplen, count, search_flags, &sia); ccline.cmdbuff[skiplen + patlen] = next_char; @@ -580,13 +586,15 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) FUNC_ATTR_NONNULL_ALL { int skiplen, patlen; + int search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); // Add a character from under the cursor for 'incsearch' - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); return FAIL; } @@ -603,7 +611,7 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) && !pat_has_uppercase(ccline.cmdbuff + skiplen)) { *c = mb_tolower(*c); } - if (*c == firstc + if (*c == search_delim || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c) != NULL) { // put a backslash before special characters @@ -774,11 +782,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } - // redraw the statusline for statuslines that display the current mode - // using the mode() function. + // Redraw the statusline in case it uses the current mode using the mode() + // function. if (!cmd_silent && msg_scrolled == 0) { - curwin->w_redr_status = true; - redraw_statuslines(); + bool found_one = false; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (*p_stl != NUL || *wp->w_p_stl != NUL) { + wp->w_redr_status = true; + found_one = true; + } + } + if (found_one) { + redraw_statuslines(); + } } did_emsg = false; @@ -885,7 +902,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) need_wait_return = false; } - set_string_option_direct((char_u *)"icm", -1, s->save_p_icm, OPT_FREE, + set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE, SID_NONE); State = s->save_State; setmouse(); @@ -934,7 +951,7 @@ static int command_line_execute(VimState *state, int key) if (s->c == K_EVENT || s->c == K_COMMAND) { if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + state_handle_k_event(); } else { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); } @@ -1464,13 +1481,14 @@ static int may_do_command_line_next_incsearch(int firstc, long count, bool next_match) FUNC_ATTR_NONNULL_ALL { - int skiplen, patlen; + int skiplen, patlen, search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); return OK; } @@ -1488,7 +1506,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, char_u save; - if (firstc == ccline.cmdbuff[skiplen]) { + if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); skiplen = 0; patlen = (int)STRLEN(pat); @@ -3945,6 +3963,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 +3984,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; @@ -4086,9 +4110,10 @@ ExpandOne ( } if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save); + ss = vim_strsave(orig_save ? orig_save : (char_u *)""); } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]); + ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : + xp->xp_files[findex]); } /* free old names */ @@ -5077,9 +5102,13 @@ ExpandFromContext ( } if (xp->xp_context == EXPAND_BUFFERS) return ExpandBufnames(pat, num_file, file, options); + if (xp->xp_context == EXPAND_DIFF_BUFFERS) { + return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER); + } if (xp->xp_context == EXPAND_TAGS - || xp->xp_context == EXPAND_TAGS_LISTFILES) + || xp->xp_context == EXPAND_TAGS_LISTFILES) { return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, directories); @@ -5107,6 +5136,22 @@ ExpandFromContext ( return ExpandPackAddDir(pat, num_file, file); } + // When expanding a function name starting with s:, match the <SNR>nr_ + // prefix. + char_u *tofree = NULL; + if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { + const size_t len = STRLEN(pat) + 20; + + tofree = xmalloc(len); + snprintf((char *)tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = tofree; + } + + 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) return FAIL; @@ -5180,6 +5225,7 @@ ExpandFromContext ( } vim_regfree(regmatch.regprog); + xfree(tofree); return ret; } @@ -6372,7 +6418,7 @@ int hist_type2char(int type) return '>'; } default: { - assert(false); + abort(); } } return NUL; @@ -6430,7 +6476,7 @@ static int open_cmdwin(void) cmdwin_level = ccline.level; // Create empty command-line buffer. - buf_open_scratch(0, "[Command Line]"); + buf_open_scratch(0, _("[Command Line]")); // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. set_option_value("bh", 0L, "wipe", OPT_LOCAL); curwin->w_p_rl = cmdmsg_rl; @@ -6759,6 +6805,6 @@ static void set_search_match(pos_T *t) t->col = search_match_endcol; if (t->lnum > curbuf->b_ml.ml_line_count) { t->lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index dc4395e081..3727aa5e62 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -32,6 +32,7 @@ #define WILD_IGNORE_COMPLETESLASH 0x400 #define WILD_NOERROR 0x800 // sets EW_NOERROR #define WILD_BUFLASTUSED 0x1000 +#define BUF_DIFF_FILTER 0x2000 /// Present history tables typedef enum { diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 14dac9a126..09453e100d 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -384,6 +384,19 @@ static int put_view( xfree(fname_esc); } + if (wp->w_alt_fnum) { + buf_T *const alt = buflist_findnr(wp->w_alt_fnum); + + // Set the alternate file if the buffer is listed. + if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL + && *alt->b_fname != NUL + && alt->b_p_bl + && (fputs("balt ", fd) < 0 + || ses_fname(fd, alt, flagp, true) == FAIL)) { + return FAIL; + } + } + // // Local mappings and abbreviations. // @@ -438,9 +451,9 @@ static int put_view( "let s:l = %" PRId64 " - ((%" PRId64 " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" "if s:l < 1 | let s:l = 1 | endif\n" - "exe s:l\n" + "keepjumps exe s:l\n" "normal! zt\n" - "%" PRId64 "\n", + "keepjumps %" PRId64 "\n", (int64_t)wp->w_cursor.lnum, (int64_t)(wp->w_cursor.lnum - wp->w_topline), (int64_t)(wp->w_height_inner / 2), diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index cacbeddb32..2906a2196b 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -702,6 +702,7 @@ void extmark_move_region( int new_row, colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { + curbuf->deleted_bytes2 = 0; // TODO(bfredl): this is not synced to the buffer state inside the callback. // But unless we make the undo implementation smarter, this is not ensured // anyway. 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/fileio.c b/src/nvim/fileio.c index a542bb19dd..792ef81665 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1644,8 +1644,7 @@ failed: save_file_ff(curbuf); // If editing a new file: set 'fenc' for the current buffer. // Also for ":read ++edit file". - set_string_option_direct((char_u *)"fenc", -1, fenc, - OPT_FREE | OPT_LOCAL, 0); + set_string_option_direct("fenc", -1, fenc, OPT_FREE | OPT_LOCAL, 0); } if (fenc_alloced) xfree(fenc); @@ -2002,7 +2001,7 @@ void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { char_u *fenc = enc_canonize(eap->cmd + eap->force_enc); - set_string_option_direct((char_u *)"fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); xfree(fenc); } } @@ -4281,7 +4280,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL if (os_dirname((char_u *)retval, MAXPATHL) == FAIL - || (fnamelen = strlen(retval)) == 0) { + || strlen(retval) == 0) { xfree(retval); return NULL; } @@ -4948,11 +4947,11 @@ int buf_check_timestamp(buf_T *buf) (void)msg_end(); if (emsg_silent == 0) { ui_flush(); - /* give the user some time to think about it */ - os_delay(1000L, true); + // give the user some time to think about it + os_delay(1004L, true); - /* don't redraw and erase the message */ - redraw_cmdline = FALSE; + // don't redraw and erase the message + redraw_cmdline = false; } } already_warned = TRUE; @@ -5077,7 +5076,8 @@ void buf_reload(buf_T *buf, int orig_mode) // Mark all undo states as changed. u_unchanged(curbuf); } - buf_updates_unregister_all(curbuf); + buf_updates_unload(curbuf, true); + curbuf->b_mod_set = true; } } xfree(ea.cmd); 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/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 7e78b9e9d6..d2a7c16186 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do elseif startswith(f.name, "nvim_tabpage_") then ismethod = true end + f.remote = f.remote_only or not f.lua_only + f.lua = f.lua_only or not f.remote_only + f.eval = (not f.lua_only) and (not f.remote_only) else - f.remote_only = true + f.remote = true f.since = 0 f.deprecated_since = 1 end @@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do newf.return_type = "Object" end newf.impl_name = f.name - newf.remote_only = true + newf.lua = false + newf.eval = false newf.since = 0 newf.deprecated_since = 1 functions[#functions+1] = newf @@ -192,7 +196,7 @@ end -- the real API. for i = 1, #functions do local fn = functions[i] - if fn.impl_name == nil and not fn.lua_only then + if fn.impl_name == nil and fn.remote then local args = {} output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') @@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void) for i = 1, #functions do local fn = functions[i] - if not fn.lua_only then + if fn.remote then output:write(' msgpack_rpc_add_method_handler('.. '(String) {.data = "'..fn.name..'", '.. '.size = sizeof("'..fn.name..'") - 1}, '.. @@ -492,7 +496,7 @@ local function process_function(fn) end for _, fn in ipairs(functions) do - if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then + if fn.lua or fn.name:sub(1, 4) == '_vim' then process_function(fn) end end diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index d16453530f..679895421a 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do - if not (fun.remote_only or fun.lua_only) then + if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2e2993ed26..9afce6e9d5 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -78,7 +78,7 @@ FileDescriptor *scriptin[NSCRIPT] = { NULL }; * Un-escaping is done by vgetc(). */ -#define MINIMAL_SIZE 20 /* minimal size for b_str */ +#define MINIMAL_SIZE 20 // minimal size for b_str static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; @@ -90,7 +90,7 @@ static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 }; // Second read ahead buffer. Used for redo. static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; -static int typeahead_char = 0; /* typeahead char that's not flushed */ +static int typeahead_char = 0; // typeahead char that's not flushed /* * when block_redo is TRUE redo buffer will not be changed @@ -116,9 +116,9 @@ static bool maphash_valid = false; /* * List used for abbreviations. */ -static mapblock_T *first_abbr = NULL; /* first entry in abbrlist */ +static mapblock_T *first_abbr = NULL; // first entry in abbrlist -static int KeyNoremap = 0; /* remapping flags */ +static int KeyNoremap = 0; // remapping flags /* * Variables used by vgetorpeek() and flush_buffers() @@ -139,18 +139,18 @@ static int KeyNoremap = 0; /* remapping flags */ * typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. * (typebuf has been put in globals.h, because check_termcode() needs it). */ -#define RM_YES 0 /* tb_noremap: remap */ -#define RM_NONE 1 /* tb_noremap: don't remap */ -#define RM_SCRIPT 2 /* tb_noremap: remap local script mappings */ -#define RM_ABBR 4 /* tb_noremap: don't remap, do abbrev. */ +#define RM_YES 0 // tb_noremap: remap +#define RM_NONE 1 // tb_noremap: don't remap +#define RM_SCRIPT 2 // tb_noremap: remap local script mappings +#define RM_ABBR 4 // tb_noremap: don't remap, do abbrev. /* typebuf.tb_buf has three parts: room in front (for result of mappings), the * middle for typeahead and room for new characters (which needs to be 3 * * MAXMAPLEN) for the Amiga). */ #define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) -static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */ -static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */ +static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf +static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap static size_t last_recorded_len = 0; // number of last recorded chars @@ -707,8 +707,9 @@ static int read_redo(bool init, bool old_redo) break; } c = *p; - if (c == NUL) /* cannot happen? */ + if (c == NUL) { // cannot happen? break; + } } return c; @@ -744,14 +745,15 @@ int start_redo(long count, bool old_redo) c = read_redo(false, old_redo); - /* copy the buffer name, if present */ + // copy the buffer name, if present if (c == '"') { add_buff(&readbuf2, "\"", 1L); c = read_redo(false, old_redo); - /* if a numbered buffer is used, increment the number */ - if (c >= '1' && c < '9') - ++c; + // if a numbered buffer is used, increment the number + if (c >= '1' && c < '9') { + c++; + } add_char_buff(&readbuf2, c); // the expression register should be re-evaluated @@ -763,7 +765,7 @@ int start_redo(long count, bool old_redo) c = read_redo(false, old_redo); } - if (c == 'v') { /* redo Visual */ + if (c == 'v') { // redo Visual VIsual = curwin->w_cursor; VIsual_active = true; VIsual_select = false; @@ -780,7 +782,7 @@ int start_redo(long count, bool old_redo) add_num_buff(&readbuf2, count); } - /* copy from the redo buffer into the stuff buffer */ + // copy from the redo buffer into the stuff buffer add_char_buff(&readbuf2, c); copy_redo(old_redo); return OK; @@ -838,26 +840,25 @@ static void init_typebuf(void) } } -/* - * insert a string in position 'offset' in the typeahead buffer (for "@r" - * and ":normal" command, vgetorpeek() and check_termcode()) - * - * If noremap is REMAP_YES, new string can be mapped again. - * If noremap is REMAP_NONE, new string cannot be mapped again. - * If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, - * but abbreviations are allowed. - * If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for - * script-local mappings. - * If noremap is > 0, that many characters of the new string cannot be mapped. - * - * If nottyped is TRUE, the string does not return KeyTyped (don't use when - * offset is non-zero!). - * - * If silent is true, cmd_silent is set when the characters are obtained. - * - * return FAIL for failure, OK otherwise - */ -int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) +// Insert a string in position 'offset' in the typeahead buffer (for "@r" +// and ":normal" command, vgetorpeek() and check_termcode()) +// +// If noremap is REMAP_YES, new string can be mapped again. +// If noremap is REMAP_NONE, new string cannot be mapped again. +// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, +// but abbreviations are allowed. +// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for +// script-local mappings. +// If noremap is > 0, that many characters of the new string cannot be mapped. +// +// If nottyped is true, the string does not return KeyTyped (don't use when +// offset is non-zero!). +// +// If silent is true, cmd_silent is set when the characters are obtained. +// +// return FAIL for failure, OK otherwise +int ins_typebuf(char_u *str, int noremap, int offset, + bool nottyped, bool silent) { char_u *s1, *s2; int newlen; @@ -890,8 +891,8 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) // often. newoff = MAXMAPLEN + 4; newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4); - if (newlen < 0) { /* string is getting too long */ - EMSG(_(e_toocompl)); /* also calls flush_buffers */ + if (newlen < 0) { // string is getting too long + EMSG(_(e_toocompl)); // also calls flush_buffers setcursor(); return FAIL; } @@ -927,13 +928,14 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) } typebuf.tb_len += addlen; - /* If noremap == REMAP_SCRIPT: do remap script-local mappings. */ - if (noremap == REMAP_SCRIPT) + // If noremap == REMAP_SCRIPT: do remap script-local mappings. + if (noremap == REMAP_SCRIPT) { val = RM_SCRIPT; - else if (noremap == REMAP_SKIP) + } else if (noremap == REMAP_SKIP) { val = RM_ABBR; - else + } else { val = RM_NONE; + } /* * Adjust typebuf.tb_noremap[] for the new characters: @@ -962,8 +964,9 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) typebuf.tb_silent += addlen; cmd_silent = true; } - if (typebuf.tb_no_abbr_cnt && offset == 0) /* and not used for abbrev.s */ + if (typebuf.tb_no_abbr_cnt && offset == 0) { // and not used for abbrev.s typebuf.tb_no_abbr_cnt += addlen; + } return OK; } @@ -997,9 +1000,8 @@ void ins_char_typebuf(int c) * Or "typebuf.tb_off" may have been changed and we would overwrite characters * that was just added. */ -int -typebuf_changed ( - int tb_change_cnt /* old value of typebuf.tb_change_cnt */ +bool typebuf_changed( + int tb_change_cnt // old value of typebuf.tb_change_cnt ) { return tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt @@ -1031,8 +1033,9 @@ void del_typebuf(int len, int offset) { int i; - if (len == 0) - return; /* nothing to do */ + if (len == 0) { + return; // nothing to do + } typebuf.tb_len -= len; @@ -1068,23 +1071,26 @@ void del_typebuf(int len, int offset) (size_t)(typebuf.tb_len - offset)); } - if (typebuf.tb_maplen > offset) { /* adjust tb_maplen */ - if (typebuf.tb_maplen < offset + len) + if (typebuf.tb_maplen > offset) { // adjust tb_maplen + if (typebuf.tb_maplen < offset + len) { typebuf.tb_maplen = offset; - else + } else { typebuf.tb_maplen -= len; + } } - if (typebuf.tb_silent > offset) { /* adjust tb_silent */ - if (typebuf.tb_silent < offset + len) + if (typebuf.tb_silent > offset) { // adjust tb_silent + if (typebuf.tb_silent < offset + len) { typebuf.tb_silent = offset; - else + } else { typebuf.tb_silent -= len; + } } - if (typebuf.tb_no_abbr_cnt > offset) { /* adjust tb_no_abbr_cnt */ - if (typebuf.tb_no_abbr_cnt < offset + len) + if (typebuf.tb_no_abbr_cnt > offset) { // adjust tb_no_abbr_cnt + if (typebuf.tb_no_abbr_cnt < offset + len) { typebuf.tb_no_abbr_cnt = offset; - else + } else { typebuf.tb_no_abbr_cnt -= len; + } } /* Reset the flag that text received from a client or from feedkeys() @@ -1135,8 +1141,8 @@ static void gotchars(const char_u *chars, size_t len) may_sync_undo(); - /* output "debug mode" message next time in debug mode */ - debug_did_msg = FALSE; + // output "debug mode" message next time in debug mode + debug_did_msg = false; /* Since characters have been typed, consider the following to be in * another mapping. Search string will be kept in history. */ @@ -1253,10 +1259,9 @@ void restore_typeahead(tasave_T *tp) /* * Open a new script file for the ":source!" command. */ -void -openscript ( +void openscript( char_u *name, - int directly /* when TRUE execute directly */ + bool directly // when true execute directly ) { if (curscript + 1 == NSCRIPT) { @@ -1275,9 +1280,10 @@ openscript ( return; } - if (scriptin[curscript] != NULL) /* already reading script */ - ++curscript; - /* use NameBuff for expanded name */ + if (scriptin[curscript] != NULL) { // already reading script + curscript++; + } + // use NameBuff for expanded name expand_env(name, NameBuff, MAXPATHL); int error; if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, @@ -1306,11 +1312,11 @@ openscript ( int save_msg_scroll = msg_scroll; State = NORMAL; - msg_scroll = FALSE; /* no msg scrolling in Normal mode */ - restart_edit = 0; /* don't go to Insert mode */ - p_im = FALSE; /* don't use 'insertmode' */ + msg_scroll = false; // no msg scrolling in Normal mode + restart_edit = 0; // don't go to Insert mode + p_im = false; // don't use 'insertmode' clear_oparg(&oa); - finish_op = FALSE; + finish_op = false; oldcurscript = curscript; do { @@ -1626,10 +1632,8 @@ int char_avail(void) return retval != NUL; } -void -vungetc ( /* unget one character (can only be done once!) */ - int c -) +// unget one character (can only be done once!) +void vungetc(int c) { old_char = c; old_mod_mask = mod_mask; @@ -1669,10 +1673,10 @@ static int vgetorpeek(bool advance) mapblock_T *mp2; mapblock_T *mp_match; int mp_match_len = 0; - int timedout = FALSE; /* waited for more than 1 second - for mapping to complete */ - int mapdepth = 0; /* check for recursive mapping */ - int mode_deleted = FALSE; /* set when mode has been deleted */ + bool timedout = false; // waited for more than 1 second + // for mapping to complete + int mapdepth = 0; // check for recursive mapping + bool mode_deleted = false; // set when mode has been deleted int local_State; int mlen; int max_mlen; @@ -1729,8 +1733,9 @@ static int vgetorpeek(bool advance) // needed for CTRL-W CTRL-] to open a fold, for example. KeyStuffed = true; } - if (typebuf.tb_no_abbr_cnt == 0) - typebuf.tb_no_abbr_cnt = 1; /* no abbreviations now */ + if (typebuf.tb_no_abbr_cnt == 0) { + typebuf.tb_no_abbr_cnt = 1; // no abbreviations now + } } else { /* * Loop until we either find a matching mapped key, or we @@ -1744,10 +1749,11 @@ static int vgetorpeek(bool advance) * inside a mapping. But call it each time for typed * characters. */ - if (typebuf.tb_maplen) + if (typebuf.tb_maplen) { line_breakcheck(); - else - os_breakcheck(); /* check for CTRL-C */ + } else { + os_breakcheck(); // check for CTRL-C + } keylen = 0; if (got_int) { // flush all input @@ -1814,11 +1820,11 @@ static int vgetorpeek(bool advance) && get_real_state() != SELECTMODE); nolmaplen = 0; } - /* First try buffer-local mappings. */ + // First try buffer-local mappings. mp = curbuf->b_maphash[MAP_HASH(local_State, c1)]; mp2 = maphash[MAP_HASH(local_State, c1)]; if (mp == NULL) { - /* There are no buffer-local mappings. */ + // There are no buffer-local mappings. mp = mp2; mp2 = NULL; } @@ -1845,8 +1851,8 @@ static int vgetorpeek(bool advance) || typebuf.tb_maplen == 0)) { int nomap = nolmaplen; int c2; - /* find the match length of this mapping */ - for (mlen = 1; mlen < typebuf.tb_len; ++mlen) { + // find the match length of this mapping + for (mlen = 1; mlen < typebuf.tb_len; mlen++) { c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; if (nomap > 0) --nomap; @@ -1901,7 +1907,7 @@ static int vgetorpeek(bool advance) if (keylen > typebuf.tb_len) { if (!timedout && !(mp_match != NULL && mp_match->m_nowait)) { - /* break at a partly match */ + // break at a partly match keylen = KEYLEN_PART_MAP; break; } @@ -1953,13 +1959,14 @@ static int vgetorpeek(bool advance) setcursor(); continue; } - /* Need more chars for partly match. */ - if (mlen == typebuf.tb_len) + // Need more chars for partly match. + if (mlen == typebuf.tb_len) { keylen = KEYLEN_PART_KEY; - else if (max_mlen < mlen) - /* no match, may have to check for termcode at - * next character */ + } else if (max_mlen < mlen) { + // no match, may have to check for termcode at + // next character max_mlen = mlen + 1; + } } if ((mp == NULL || max_mlen >= mp_match_len) @@ -1991,13 +1998,11 @@ static int vgetorpeek(bool advance) } } - /* complete match */ + // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { int save_m_expr; int save_m_noremap; int save_m_silent; - char_u *save_m_keys; - char_u *save_m_str; // Write chars to script file(s) // Note: :lmap mappings are written *after* being applied. #5658 @@ -2007,7 +2012,7 @@ static int vgetorpeek(bool advance) } cmd_silent = (typebuf.tb_silent > 0); - del_typebuf(keylen, 0); /* remove the mapped keys */ + del_typebuf(keylen, 0); // remove the mapped keys /* * Put the replacement string in front of mapstr. @@ -2032,9 +2037,8 @@ static int vgetorpeek(bool advance) */ if (VIsual_active && VIsual_select && (mp->m_mode & VISUAL)) { - VIsual_select = FALSE; - (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, - 0, TRUE, FALSE); + VIsual_select = false; + (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); } /* Copy the values from *mp that are used, because @@ -2044,8 +2048,8 @@ static int vgetorpeek(bool advance) save_m_expr = mp->m_expr; save_m_noremap = mp->m_noremap; save_m_silent = mp->m_silent; - save_m_keys = NULL; /* only saved when needed */ - save_m_str = NULL; /* only saved when needed */ + char_u *save_m_keys = NULL; // only saved when needed + char_u *save_m_str = NULL; // only saved when needed /* * Handle ":map <expr>": evaluate the {rhs} as an @@ -2141,14 +2145,14 @@ static int vgetorpeek(bool advance) char_u *ptr; if (mode_displayed) { - unshowmode(TRUE); - mode_deleted = TRUE; + unshowmode(true); + mode_deleted = true; } validate_cursor(); old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; - /* move cursor left, if possible */ + // move cursor left, if possible if (curwin->w_cursor.col != 0) { if (curwin->w_wcol > 0) { if (did_ai) { @@ -2170,7 +2174,7 @@ static int vgetorpeek(bool advance) + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); - col = 0; /* no correction needed */ + col = 0; // no correction needed } else { --curwin->w_wcol; col = curwin->w_cursor.col - 1; @@ -2197,8 +2201,9 @@ static int vgetorpeek(bool advance) curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; } - if (c < 0) - continue; /* end of input script reached */ + if (c < 0) { + continue; // end of input script reached + } // Allow mapping for just typed characters. When we get here c // is the number of extra bytes and typebuf.tb_len is 1. @@ -2207,20 +2212,20 @@ static int vgetorpeek(bool advance) } typebuf.tb_len += c; - /* buffer full, don't map */ + // buffer full, don't map if (typebuf.tb_len >= typebuf.tb_maplen + MAXMAPLEN) { - timedout = TRUE; + timedout = true; continue; } if (ex_normal_busy > 0) { static int tc = 0; - /* No typeahead left and inside ":normal". Must return - * something to avoid getting stuck. When an incomplete - * mapping is present, behave like it timed out. */ + // No typeahead left and inside ":normal". Must return + // something to avoid getting stuck. When an incomplete + // mapping is present, behave like it timed out. if (typebuf.tb_len > 0) { - timedout = TRUE; + timedout = true; continue; } /* When 'insertmode' is set, ESC just beeps in Insert @@ -2254,7 +2259,7 @@ static int vgetorpeek(bool advance) if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { update_screen(0); - setcursor(); /* put cursor back where it belongs */ + setcursor(); // put cursor back where it belongs } /* @@ -2267,16 +2272,16 @@ static int vgetorpeek(bool advance) if (typebuf.tb_len > 0 && advance && !exmode_active) { if (((State & (NORMAL | INSERT)) || State == LANGMAP) && State != HITRETURN) { - /* this looks nice when typing a dead character map */ + // this looks nice when typing a dead character map if (State & INSERT && ptr2cells(typebuf.tb_buf + typebuf.tb_off - + typebuf.tb_len - 1) == 1) { - edit_putchar(typebuf.tb_buf[typebuf.tb_off - + typebuf.tb_len - 1], FALSE); - setcursor(); /* put cursor back where it belongs */ + + typebuf.tb_len - 1) == 1) { + edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], + false); + setcursor(); // put cursor back where it belongs c1 = 1; } - /* need to use the col and row from above here */ + // need to use the col and row from above here old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; curwin->w_wcol = new_wcol; @@ -2332,31 +2337,34 @@ static int vgetorpeek(bool advance) if (i != 0) pop_showcmd(); if (c1 == 1) { - if (State & INSERT) + if (State & INSERT) { edit_unputchar(); - if (State & CMDLINE) + } + if (State & CMDLINE) { unputcmdline(); - else - setcursor(); /* put cursor back where it belongs */ + } else { + setcursor(); // put cursor back where it belongs + } } - if (c < 0) - continue; /* end of input script reached */ - if (c == NUL) { /* no character available */ - if (!advance) + if (c < 0) { + continue; // end of input script reached + } + if (c == NUL) { // no character available + if (!advance) { break; - if (wait_tb_len > 0) { /* timed out */ - timedout = TRUE; + } + if (wait_tb_len > 0) { // timed out + timedout = true; continue; } - } else { /* allow mapping for just typed characters */ - while (typebuf.tb_buf[typebuf.tb_off - + typebuf.tb_len] != NUL) - typebuf.tb_noremap[typebuf.tb_off - + typebuf.tb_len++] = RM_YES; + } else { // allow mapping for just typed characters + while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) { + typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES; + } } - } /* for (;;) */ - } /* if (!character from stuffbuf) */ + } // for (;;) + } // if (!character from stuffbuf) // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); @@ -2368,15 +2376,17 @@ static int vgetorpeek(bool advance) */ if (advance && p_smd && msg_silent == 0 && (State & INSERT)) { if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { - if (typebuf.tb_len && !KeyTyped) - redraw_cmdline = TRUE; /* delete mode later */ - else - unshowmode(FALSE); + if (typebuf.tb_len && !KeyTyped) { + redraw_cmdline = true; // delete mode later + } else { + unshowmode(false); + } } else if (c != ESC && mode_deleted) { - if (typebuf.tb_len && !KeyTyped) - redraw_cmdline = TRUE; /* show mode later */ - else + if (typebuf.tb_len && !KeyTyped) { + redraw_cmdline = true; // show mode later + } else { showmode(); + } } } @@ -2440,8 +2450,8 @@ int inchar( * on char wait, flush swapfile, write error....). */ if (State != HITRETURN) { - did_outofmem_msg = FALSE; /* display out of memory message (again) */ - did_swapwrite_msg = FALSE; /* display swap file write error again */ + did_outofmem_msg = false; // display out of memory message (again) + did_swapwrite_msg = false; // display swap file write error again } // Get a character from a script file if there is one. @@ -3175,7 +3185,7 @@ static void validate_maphash(void) /* * Get the mapping mode from the command name. */ -int get_map_mode(char_u **cmdp, int forceit) +int get_map_mode(char_u **cmdp, bool forceit) { char_u *p; int modec; @@ -3183,30 +3193,31 @@ int get_map_mode(char_u **cmdp, int forceit) p = *cmdp; modec = *p++; - if (modec == 'i') - mode = INSERT; /* :imap */ - else if (modec == 'l') - mode = LANGMAP; /* :lmap */ - else if (modec == 'c') - mode = CMDLINE; /* :cmap */ - else if (modec == 'n' && *p != 'o') /* avoid :noremap */ - mode = NORMAL; /* :nmap */ - else if (modec == 'v') - mode = VISUAL + SELECTMODE; /* :vmap */ - else if (modec == 'x') - mode = VISUAL; /* :xmap */ - else if (modec == 's') - mode = SELECTMODE; /* :smap */ - else if (modec == 'o') - mode = OP_PENDING; /* :omap */ - else if (modec == 't') - mode = TERM_FOCUS; // :tmap - else { - --p; - if (forceit) - mode = INSERT + CMDLINE; /* :map ! */ - else - mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; /* :map */ + if (modec == 'i') { + mode = INSERT; // :imap + } else if (modec == 'l') { + mode = LANGMAP; // :lmap + } else if (modec == 'c') { + mode = CMDLINE; // :cmap + } else if (modec == 'n' && *p != 'o') { // avoid :noremap + mode = NORMAL; // :nmap + } else if (modec == 'v') { + mode = VISUAL + SELECTMODE; // :vmap + } else if (modec == 'x') { + mode = VISUAL; // :xmap + } else if (modec == 's') { + mode = SELECTMODE; // :smap + } else if (modec == 'o') { + mode = OP_PENDING; // :omap + } else if (modec == 't') { + mode = TERM_FOCUS; // :tmap + } else { + p--; + if (forceit) { + mode = INSERT + CMDLINE; // :map ! + } else { + mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; // :map + } } *cmdp = p; @@ -3237,12 +3248,11 @@ void map_clear_mode(char_u *cmdp, char_u *arg, int forceit, int abbr) /* * Clear all mappings in "mode". */ -void -map_clear_int ( - buf_T *buf, /* buffer for local mappings */ - int mode, /* mode in which to delete */ - int local, /* TRUE for buffer-local mappings */ - int abbr /* TRUE for abbreviations */ +void map_clear_int( + buf_T *buf, // buffer for local mappings + int mode, // mode in which to delete + bool local, // true for buffer-local mappings + bool abbr // true for abbreviations ) { mapblock_T *mp, **mpp; @@ -3253,12 +3263,14 @@ map_clear_int ( for (hash = 0; hash < 256; ++hash) { if (abbr) { - if (hash > 0) /* there is only one abbrlist */ + if (hash > 0) { // there is only one abbrlist break; - if (local) + } + if (local) { mpp = &buf->b_first_abbr; - else + } else { mpp = &first_abbr; + } } else { if (local) mpp = &buf->b_maphash[hash]; @@ -3286,7 +3298,7 @@ map_clear_int ( mp->m_next = maphash[new_hash]; maphash[new_hash] = mp; } - continue; /* continue with *mpp */ + continue; // continue with *mpp } } mpp = &(mp->m_next); @@ -3341,10 +3353,9 @@ char *map_mode_to_chars(int mode) return (char *)mapmode.ga_data; } -static void -showmap ( +static void showmap( mapblock_T *mp, - int local /* TRUE for buffer-local map */ + bool local // true for buffer-local map ) { size_t len = 1; @@ -3355,8 +3366,9 @@ showmap ( if (msg_didout || msg_silent != 0) { msg_putchar('\n'); - if (got_int) /* 'q' typed at MORE prompt */ + if (got_int) { // 'q' typed at MORE prompt return; + } } { @@ -3372,8 +3384,8 @@ showmap ( // Display the LHS. Get length of what we write. len = (size_t)msg_outtrans_special(mp->m_keys, true, 0); do { - msg_putchar(' '); /* padd with blanks */ - ++len; + msg_putchar(' '); // padd with blanks + len++; } while (len < 12); if (mp->m_noremap == REMAP_NONE) { @@ -3506,21 +3518,20 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) * Used below when expanding mapping/abbreviation names. */ static int expand_mapmodes = 0; -static int expand_isabbrev = 0; -static int expand_buffer = FALSE; +static bool expand_isabbrev = false; +static bool expand_buffer = false; /* * Work out what to complete when doing command line completion of mapping * or abbreviation names. */ -char_u * -set_context_in_map_cmd ( +char_u *set_context_in_map_cmd( expand_T *xp, char_u *cmd, char_u *arg, - int forceit, /* TRUE if '!' given */ - int isabbrev, /* TRUE if abbreviation */ - int isunmap, /* TRUE if unmap/unabbrev command */ + bool forceit, // true if '!' given + bool isabbrev, // true if abbreviation + bool isunmap, // true if unmap/unabbrev command cmdidx_T cmdidx ) { @@ -3536,10 +3547,10 @@ set_context_in_map_cmd ( } expand_isabbrev = isabbrev; xp->xp_context = EXPAND_MAPPINGS; - expand_buffer = FALSE; + expand_buffer = false; for (;; ) { if (STRNCMP(arg, "<buffer>", 8) == 0) { - expand_buffer = TRUE; + expand_buffer = true; arg = skipwhite(arg + 8); continue; } @@ -3589,7 +3600,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) validate_maphash(); - *num_file = 0; /* return values in case of FAIL */ + *num_file = 0; // return values in case of FAIL *file = NULL; /* @@ -3628,8 +3639,9 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (hash = 0; hash < 256; ++hash) { if (expand_isabbrev) { - if (hash > 0) /* only one abbrev list */ - break; /* for (hash) */ + if (hash > 0) { // only one abbrev list + break; // for (hash) + } mp = first_abbr; } else if (expand_buffer) mp = curbuf->b_maphash[hash]; @@ -3648,26 +3660,27 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) } xfree(p); } - } /* for (mp) */ - } /* for (hash) */ + } // for (mp) + } // for (hash) - if (count == 0) /* no match found */ - break; /* for (round) */ + if (count == 0) { // no match found + break; // for (round) + } if (round == 1) { *file = (char_u **)xmalloc((size_t)count * sizeof(char_u *)); } - } /* for (round) */ + } // for (round) if (count > 1) { char_u **ptr1; char_u **ptr2; char_u **ptr3; - /* Sort the matches */ + // Sort the matches sort_strings(*file, count); - /* Remove multiple entries */ + // Remove multiple entries ptr1 = *file; ptr2 = ptr1 + 1; ptr3 = ptr1 + count; @@ -3705,7 +3718,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) bool check_abbr(int c, char_u *ptr, int col, int mincol) { int len; - int scol; /* starting column of the abbr. */ + int scol; // starting column of the abbr. int j; char_u *s; char_u tb[MB_MAXBYTES + 4]; @@ -3756,7 +3769,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) if (scol < mincol) scol = mincol; - if (scol < col) { /* there is a word in front of the cursor */ + if (scol < col) { // there is a word in front of the cursor ptr += scol; len = col - scol; mp = curbuf->b_first_abbr; @@ -3778,7 +3791,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) vim_unescape_csi(q); qlen = (int)STRLEN(q); } - /* find entries with right mode and keys */ + // find entries with right mode and keys match = (mp->m_mode & State) && qlen == len && !STRNCMP(q, ptr, (size_t)len); @@ -3805,7 +3818,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) */ j = 0; if (c != Ctrl_RSB) { - /* special key code, split up */ + // special key code, split up if (IS_SPECIAL(c) || c == K_SPECIAL) { tb[j++] = K_SPECIAL; tb[j++] = (char_u)K_SECOND(c); @@ -3821,17 +3834,17 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) j += utf_char2bytes(c, tb + j); } tb[j] = NUL; - /* insert the last typed char */ - (void)ins_typebuf(tb, 1, 0, TRUE, mp->m_silent); + // insert the last typed char + (void)ins_typebuf(tb, 1, 0, true, mp->m_silent); } if (mp->m_expr) s = eval_map_expr(mp->m_str, c); else s = mp->m_str; if (s != NULL) { - /* insert the to string */ - (void)ins_typebuf(s, mp->m_noremap, 0, TRUE, mp->m_silent); - /* no abbrev. for these chars */ + // insert the to string + (void)ins_typebuf(s, mp->m_noremap, 0, true, mp->m_silent); + // no abbrev. for these chars typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1; if (mp->m_expr) xfree(s); @@ -3856,7 +3869,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) static char_u * eval_map_expr ( char_u *str, - int c /* NUL or typed character for abbreviation */ + int c // NUL or typed character for abbreviation ) { char_u *res; @@ -3874,11 +3887,11 @@ eval_map_expr ( save_cmd = save_cmdline_alloc(); - /* Forbid changing text or using ":normal" to avoid most of the bad side - * effects. Also restore the cursor position. */ - ++textlock; - ++ex_normal_lock; - set_vim_var_char(c); /* set v:char to the typed character */ + // Forbid changing text or using ":normal" to avoid most of the bad side + // effects. Also restore the cursor position. + textlock++; + ex_normal_lock++; + set_vim_var_char(c); // set v:char to the typed character save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; @@ -3894,7 +3907,7 @@ eval_map_expr ( if (p == NULL) return NULL; - /* Escape CSI in the result to be able to use the string as typeahead. */ + // Escape CSI in the result to be able to use the string as typeahead. res = vim_strsave_escape_csi(p); xfree(p); @@ -3914,7 +3927,7 @@ char_u *vim_strsave_escape_csi(char_u *p) char_u *d = res; for (char_u *s = p; *s != NUL; ) { if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) { - /* Copy special key unmodified. */ + // Copy special key unmodified. *d++ = *s++; *d++ = *s++; *d++ = *s++; @@ -4213,9 +4226,10 @@ int put_escstr(FILE *fd, char_u *strstart, int what) c = TO_SPECIAL(str[1], str[2]); str += 2; } - if (IS_SPECIAL(c) || modifiers) { /* special key */ - if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) + if (IS_SPECIAL(c) || modifiers) { // special key + if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) { return FAIL; + } continue; } } @@ -4271,35 +4285,36 @@ char_u * check_map ( char_u *keys, int mode, - int exact, /* require exact match */ - int ign_mod, /* ignore preceding modifier */ - int abbr, /* do abbreviations */ - mapblock_T **mp_ptr, /* return: pointer to mapblock or NULL */ - int *local_ptr /* return: buffer-local mapping or NULL */ + int exact, // require exact match + int ign_mod, // ignore preceding modifier + int abbr, // do abbreviations + mapblock_T **mp_ptr, // return: pointer to mapblock or NULL + int *local_ptr // return: buffer-local mapping or NULL ) { - int hash; int len, minlen; mapblock_T *mp; - int local; validate_maphash(); len = (int)STRLEN(keys); - for (local = 1; local >= 0; --local) - /* loop over all hash lists */ - for (hash = 0; hash < 256; ++hash) { + for (int local = 1; local >= 0; local--) { + // loop over all hash lists + for (int hash = 0; hash < 256; hash++) { if (abbr) { - if (hash > 0) /* there is only one list. */ + if (hash > 0) { // there is only one list. break; - if (local) + } + if (local) { mp = curbuf->b_first_abbr; - else + } else { mp = first_abbr; - } else if (local) + } + } else if (local) { mp = curbuf->b_maphash[hash]; - else + } else { mp = maphash[hash]; + } for (; mp != NULL; mp = mp->m_next) { /* skip entries with wrong mode, wrong length and not matching * ones */ @@ -4322,6 +4337,7 @@ check_map ( } } } + } return NULL; } @@ -4336,7 +4352,7 @@ void add_map(char_u *map, int mode) char_u *s; char_u *cpo_save = p_cpo; - p_cpo = (char_u *)""; /* Allow <> notation */ + p_cpo = (char_u *)""; // Allow <> notation s = vim_strsave(map); (void)do_map(0, s, mode, FALSE); xfree(s); @@ -4384,7 +4400,7 @@ static char_u * translate_mapping ( } if (IS_SPECIAL(c) || modifiers) { // special key ga_concat(&ga, get_special_key_name(c, modifiers)); - continue; /* for (str) */ + continue; // for (str) } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 3b8f4116b7..624b7c93f3 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -180,6 +180,11 @@ EXTERN int compl_cont_status INIT(= 0); # define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local // expansion, (eg use complete=.) +EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info + // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left EXTERN int msg_col; @@ -251,7 +256,7 @@ EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level -EXTERN int debug_did_msg INIT(= false); // did "debug mode" message +EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message EXTERN int debug_tick INIT(= 0); // breakpoint change count EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level @@ -328,9 +333,10 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_ENV -4 // for sourcing environment variable #define SID_ERROR -5 // option was reset because of an error #define SID_NONE -6 // don't set scriptID -#define SID_LUA -7 // for Lua scripts/chunks -#define SID_API_CLIENT -8 // for API clients -#define SID_STR -9 // for sourcing a string +#define SID_WINLAYOUT -7 // changing window size +#define SID_LUA -8 // for Lua scripts/chunks +#define SID_API_CLIENT -9 // for API clients +#define SID_STR -10 // for sourcing a string // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); @@ -463,7 +469,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer // Iterate through all the signs placed in a buffer #define FOR_ALL_SIGNS_IN_BUF(buf, sign) \ - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT + for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT // List of files being edited (global argument list). curwin->w_alist points @@ -639,10 +645,6 @@ EXTERN int arrow_used; // Normally false, set to true after // to call u_sync() EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when // restarting edit after CTRL-O -EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode -EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode -EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode -EXTERN hlf_T edit_submode_highl; // highl. method for extra info EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded @@ -1009,6 +1011,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/grid_defs.h b/src/nvim/grid_defs.h index e14aae73d8..3b34af46e4 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -7,7 +7,7 @@ #include "nvim/types.h" -#define MAX_MCO 6 // maximum value for 'maxcombine' +#define MAX_MCO 6 // fixed value for 'maxcombine' // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; @@ -35,7 +35,8 @@ typedef int sattr_T; /// line_wraps[] is an array of boolean flags indicating if the screen line /// wraps to the next line. It can only be true if a window occupies the entire /// screen width. -typedef struct { +typedef struct ScreenGrid ScreenGrid; +struct ScreenGrid { handle_T handle; schar_T *chars; @@ -58,10 +59,13 @@ typedef struct { // external UI. bool throttled; - // offsets for the grid relative to the global screen. Used by screen.c - // for windows that don't have w_grid->chars etc allocated + // TODO(bfredl): maybe physical grids and "views" (i e drawing + // specifications) should be two separate types? + // offsets for the grid relative to another grid. Used for grids + // that are views into another, actually allocated grid 'target' int row_offset; int col_offset; + ScreenGrid *target; // whether the compositor should blend the grid with the background grid bool blending; @@ -76,6 +80,12 @@ typedef struct { int comp_row; int comp_col; + // Requested width and height of the grid upon resize. Used by + // `ui_compositor` to correctly determine which regions need to + // be redrawn. + int comp_width; + int comp_height; + // z-index of the grid. Grids with higher index is draw on top. // default_grid.comp_index is always zero. size_t comp_index; @@ -83,9 +93,10 @@ typedef struct { // compositor should momentarily ignore the grid. Used internally when // moving around grids etc. bool comp_disabled; -} ScreenGrid; +}; #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ - false, 0, 0, false, true, 0, 0, 0, false } + false, 0, 0, NULL, false, true, \ + 0, 0, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 4ec949759c..abba5425e7 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -890,8 +890,11 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T * Appropriately expand any tabs to spaces. */ if (line[col] == TAB || tab_spaces != 0) { - if (tab_spaces == 0) - tab_spaces = (int)(curbuf->b_p_ts - (print_pos % curbuf->b_p_ts)); + if (tab_spaces == 0) { + tab_spaces = tabstop_padding(print_pos, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + } while (tab_spaces > 0) { need_break = mch_print_text_out((char_u *)" ", 1); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index b01cdde236..79801262cb 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -8,6 +8,7 @@ #include "nvim/highlight_defs.h" #include "nvim/map.h" #include "nvim/message.h" +#include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/syntax.h" @@ -151,7 +152,7 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) { - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) { return; @@ -175,7 +176,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) ns_id = ns_hl_active; } - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? bool valid_cache = it.version >= p->hl_valid; @@ -341,6 +342,25 @@ void update_window_hl(win_T *wp, bool invalid) } wp->w_hl_attrs[hlf] = attr; } + + wp->w_float_config.shadow = false; + if (wp->w_floating && wp->w_float_config.border) { + for (int i = 0; i < 8; i++) { + int attr = wp->w_hl_attrs[HLF_BORDER]; + if (wp->w_float_config.border_hl_ids[i]) { + attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], + false); + HlAttrs a = syn_attr2entry(attr); + if (a.hl_blend) { + wp->w_float_config.shadow = true; + } + } + wp->w_float_config.border_attr[i] = attr; + } + } + + // shadow might cause blending + check_blending(wp); } /// Gets HL_UNDERLINE highlight. @@ -517,6 +537,10 @@ static HlAttrs get_colors_force(int attr) /// @return the resulting attributes. int hl_blend_attrs(int back_attr, int front_attr, bool *through) { + if (front_attr < 0 || back_attr < 0) { + return -1; + } + HlAttrs fattrs = get_colors_force(front_attr); int ratio = fattrs.hl_blend; if (ratio <= 0) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 2bda094d8e..ed4aefb577 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -101,6 +101,7 @@ typedef enum { , HLF_MSGSEP // message separator line , HLF_NFLOAT // Floating window , HLF_MSG // Message area + , HLF_BORDER // Floating window border , HLF_COUNT // MUST be the last one } hlf_T; @@ -155,6 +156,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MSGSEP] = "MsgSeparator", [HLF_NFLOAT] = "NormalFloat", [HLF_MSG] = "MsgArea", + [HLF_BORDER] = "FloatBorder", }); diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 2dad8fb781..31615e744a 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1865,7 +1865,7 @@ static void cs_release_csp(size_t i, bool freefnpp) alive = false; // cscope process no longer exists break; } - os_delay(50L, false); // sleep 50ms + os_delay(50L, false); // sleep 50 ms } } if (alive) diff --git a/src/nvim/indent.c b/src/nvim/indent.c index fae971b3b3..8fa61515ef 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -34,14 +34,20 @@ // Count the size (in window cells) of the indent in the current line. int get_indent(void) { - return get_indent_str(get_cursor_line_ptr(), (int)curbuf->b_p_ts, false); + return get_indent_str_vtab(get_cursor_line_ptr(), + curbuf->b_p_ts, + curbuf->b_p_vts_array, + false); } // Count the size (in window cells) of the indent in line "lnum". int get_indent_lnum(linenr_T lnum) { - return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, false); + return get_indent_str_vtab(ml_get(lnum), + curbuf->b_p_ts, + curbuf->b_p_vts_array, + false); } @@ -49,7 +55,10 @@ int get_indent_lnum(linenr_T lnum) // "buf". int get_indent_buf(buf_T *buf, linenr_T lnum) { - return get_indent_str(ml_get_buf(buf, lnum, false), (int)buf->b_p_ts, false); + return get_indent_str_vtab(ml_get_buf(buf, lnum, false), + curbuf->b_p_ts, + buf->b_p_vts_array, + false); } @@ -82,6 +91,30 @@ int get_indent_str(const char_u *ptr, int ts, int list) return count; } +// Count the size (in window cells) of the indent in line "ptr", using +// variable tabstops. +// if "list" is true, count only screen size for tabs. +int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) +{ + int count = 0; + + for (; *ptr; ptr++) { + if (*ptr == TAB) { // count a tab for what it is worth + if (!list || curwin->w_p_lcs_chars.tab1) { + count += tabstop_padding(count, ts, vts); + } else { + // In list mode, when tab is not set, count screen char width + // for Tab, displays: ^I + count += ptr2cells(ptr); + } + } else if (*ptr == ' ') { + count++; // count a space for one + } else { + break; + } + } + return count; +} // Set the indent of the current line. // Leaves the cursor on the first non-blank in the line. @@ -104,6 +137,7 @@ int set_indent(int size, int flags) int line_len; int doit = false; int ind_done = 0; // Measured in spaces. + int ind_col = 0; int tab_pad; int retval = false; @@ -130,7 +164,9 @@ int set_indent(int size, int flags) // Count as many characters as we can use. while (todo > 0 && ascii_iswhite(*p)) { if (*p == TAB) { - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -147,35 +183,41 @@ int set_indent(int size, int flags) p++; } + // These diverge from this point. + ind_col = ind_done; // Set initial number of whitespace chars to copy if we are // preserving indent but expandtab is set. if (curbuf->b_p_et) { orig_char_len = ind_len; } - // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); - + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); if ((todo >= tab_pad) && (orig_char_len == -1)) { doit = true; todo -= tab_pad; ind_len++; // ind_done += tab_pad; + ind_col += tab_pad; } } // Count tabs required for indent. - while (todo >= (int)curbuf->b_p_ts) { + for (;;) { + tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } if (*p != TAB) { doit = true; } else { p++; } - todo -= (int)curbuf->b_p_ts; + todo -= tab_pad; ind_len++; - - // ind_done += (int)curbuf->b_p_ts; + ind_col += tab_pad; } } @@ -255,7 +297,9 @@ int set_indent(int size, int flags) while (todo > 0 && ascii_iswhite(*p)) { if (*p == TAB) { - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -272,18 +316,28 @@ int set_indent(int size, int flags) } // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); if (todo >= tab_pad) { *s++ = TAB; todo -= tab_pad; + ind_done += tab_pad; } p = skipwhite(p); } - while (todo >= (int)curbuf->b_p_ts) { + for (;;) { + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } *s++ = TAB; - todo -= (int)curbuf->b_p_ts; + todo -= tab_pad; + ind_done += tab_pad; } } @@ -375,11 +429,9 @@ int get_number_indent(linenr_T lnum) return (int)col; } -/* - * Return appropriate space number for breakindent, taking influencing - * parameters into account. Window must be specified, since it is not - * necessarily always the current one. - */ +// Return appropriate space number for breakindent, taking influencing +// parameters into account. Window must be specified, since it is not +// necessarily always the current one. int get_breakindent_win(win_T *wp, const char_u *line) FUNC_ATTR_NONNULL_ALL { @@ -387,6 +439,7 @@ int get_breakindent_win(win_T *wp, const char_u *line) static long prev_ts = 0; // Cached tabstop value. static const char_u *prev_line = NULL; // cached pointer to line. static varnumber_T prev_tick = 0; // Changedtick of cached value. + static long *prev_vts = NULL; // Cached vartabs values. int bri = 0; // window width minus window margin space, i.e. what rests for text const int eff_wwidth = wp->w_width_inner @@ -396,11 +449,16 @@ int get_breakindent_win(win_T *wp, const char_u *line) // used cached indent, unless pointer or 'tabstop' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts - || prev_tick != buf_get_changedtick(wp->w_buffer)) { + || prev_tick != buf_get_changedtick(wp->w_buffer) + || prev_vts != wp->w_buffer->b_p_vts_array) { prev_line = line; prev_ts = wp->w_buffer->b_p_ts; prev_tick = buf_get_changedtick(wp->w_buffer); - prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list); + prev_vts = wp->w_buffer->b_p_vts_array; + prev_indent = get_indent_str_vtab(line, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array, + wp->w_p_list); } bri = prev_indent + wp->w_briopt_shift; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 9298e57411..771bf923b2 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -33,15 +33,12 @@ typedef struct { * Search starts at w_cursor.lnum and goes backwards. * Return NULL when not inside a comment. */ -static pos_T *ind_find_start_comment(void) -{ /* XXX */ +static pos_T *ind_find_start_comment(void) // XXX +{ return find_start_comment(curbuf->b_ind_maxcomment); } -pos_T * -find_start_comment ( /* XXX */ - int ind_maxcomment -) +pos_T *find_start_comment(int ind_maxcomment) // XXX { pos_T *pos; char_u *line; @@ -109,8 +106,8 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) * Search starts at w_cursor.lnum and goes backwards. * Return NULL when not inside a raw string. */ -static pos_T *find_start_rawstring(int ind_maxcomment) -{ /* XXX */ +static pos_T *find_start_rawstring(int ind_maxcomment) // XXX +{ pos_T *pos; char_u *line; char_u *p; @@ -152,31 +149,35 @@ static char_u *skip_string(char_u *p) /* * We loop, because strings may be concatenated: "date""time". */ - for (;; ++p) { - if (p[0] == '\'') { /* 'c' or '\n' or '\000' */ - if (!p[1]) /* ' at end of line */ + for (;; p++) { + if (p[0] == '\'') { // 'c' or '\n' or '\000' + if (!p[1]) { // ' at end of line break; + } i = 2; - if (p[1] == '\\') { /* '\n' or '\000' */ - ++i; - while (ascii_isdigit(p[i - 1])) /* '\000' */ - ++i; + if (p[1] == '\\') { // '\n' or '\000' + i++; + while (ascii_isdigit(p[i - 1])) { // '\000' + i++; + } } - if (p[i] == '\'') { /* check for trailing ' */ + if (p[i] == '\'') { // check for trailing ' p += i; continue; } - } else if (p[0] == '"') { /* start of string */ - for (++p; p[0]; ++p) { - if (p[0] == '\\' && p[1] != NUL) - ++p; - else if (p[0] == '"') /* end of string */ + } else if (p[0] == '"') { // start of string + for (++p; p[0]; p++) { + if (p[0] == '\\' && p[1] != NUL) { + p++; + } else if (p[0] == '"') { // end of string break; + } + } + if (p[0] == '"') { + continue; // continue for another string } - if (p[0] == '"') - continue; /* continue for another string */ } else if (p[0] == 'R' && p[1] == '"') { - /* Raw string: R"[delim](...)[delim]" */ + // Raw string: R"[delim](...)[delim]" char_u *delim = p + 2; char_u *paren = vim_strchr(delim, '('); @@ -190,14 +191,16 @@ static char_u *skip_string(char_u *p) p += delim_len + 1; break; } - if (p[0] == '"') - continue; /* continue for another string */ + if (p[0] == '"') { + continue; // continue for another string + } } } - break; /* no string found */ + break; // no string found + } + if (!*p) { + p--; // backup from NUL } - if (!*p) - --p; /* backup from NUL */ return p; } @@ -255,20 +258,22 @@ static char_u *cin_skipcomment(char_u *s) s += STRLEN(s); break; } - if (*s != '/') + if (*s != '/') { break; - ++s; - if (*s == '/') { /* slash-slash comment continues till eol */ + } + s++; + if (*s == '/') { // slash-slash comment continues till eol s += STRLEN(s); break; } if (*s != '*') break; - for (++s; *s; ++s) /* skip slash-star comment */ + for (++s; *s; s++) { // skip slash-star comment if (s[0] == '*' && s[1] == '/') { s += 2; break; } + } } return s; } @@ -285,7 +290,7 @@ static int cin_nocode(char_u *s) /* * Check previous lines for a "//" line comment, skipping over blank lines. */ -static pos_T *find_line_comment(void) /* XXX */ +static pos_T *find_line_comment(void) // XXX { static pos_T pos; char_u *line; @@ -335,39 +340,38 @@ static bool cin_has_js_key(char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static int cin_islabel_skip(char_u **s) +static bool cin_islabel_skip(char_u **s) + FUNC_ATTR_NONNULL_ALL { - if (!vim_isIDc(**s)) /* need at least one ID character */ - return FALSE; + if (!vim_isIDc(**s)) { // need at least one ID character + return false; + } while (vim_isIDc(**s)) (*s)++; *s = cin_skipcomment(*s); - /* "::" is not a label, it's C++ */ + // "::" is not a label, it's C++ return **s == ':' && *++*s != ':'; } -/* - * Recognize a label: "label:". - * Note: curwin->w_cursor must be where we are looking for the label. - */ -int cin_islabel(void) -{ /* XXX */ +// Recognize a label: "label:". +// Note: curwin->w_cursor must be where we are looking for the label. +bool cin_islabel(void) // XXX +{ char_u *s = cin_skipcomment(get_cursor_line_ptr()); - /* - * Exclude "default" from labels, since it should be indented - * like a switch label. Same for C++ scope declarations. - */ - if (cin_isdefault(s)) - return FALSE; - if (cin_isscopedecl(s)) - return FALSE; - + // Exclude "default" from labels, since it should be indented + // like a switch label. Same for C++ scope declarations. + if (cin_isdefault(s)) { + return false; + } + if (cin_isscopedecl(s)) { + return false; + } if (!cin_islabel_skip(&s)) { - return FALSE; + return false; } /* @@ -392,21 +396,24 @@ int cin_islabel(void) } line = get_cursor_line_ptr(); - if (cin_ispreproc(line)) /* ignore #defines, #if, etc. */ + if (cin_ispreproc(line)) { // ignore #defines, #if, etc. continue; - if (*(line = cin_skipcomment(line)) == NUL) + } + if (*(line = cin_skipcomment(line)) == NUL) { continue; + } curwin->w_cursor = cursor_save; if (cin_isterminated(line, TRUE, FALSE) || cin_isscopedecl(line) - || cin_iscase(line, TRUE) - || (cin_islabel_skip(&line) && cin_nocode(line))) - return TRUE; - return FALSE; + || cin_iscase(line, true) + || (cin_islabel_skip(&line) && cin_nocode(line))) { + return true; + } + return false; } curwin->w_cursor = cursor_save; - return TRUE; /* label at start of file??? */ + return true; // label at start of file??? } /* @@ -451,10 +458,9 @@ static int cin_isinit(void) /* * Recognize a switch label: "case .*:" or "default:". */ -int -cin_iscase ( +bool cin_iscase( char_u *s, - int strict /* Allow relaxed check of case statement for JS */ + bool strict // Allow relaxed check of case statement for JS ) { s = cin_skipcomment(s); @@ -465,29 +471,32 @@ cin_iscase ( break; } if (*s == ':') { - if (s[1] == ':') /* skip over "::" for C++ */ - ++s; - else - return TRUE; + if (s[1] == ':') { // skip over "::" for C++ + s++; + } else { + return true; + } } - if (*s == '\'' && s[1] && s[2] == '\'') - s += 2; /* skip over ':' */ - else if (*s == '/' && (s[1] == '*' || s[1] == '/')) - return FALSE; /* stop at comment */ - else if (*s == '"') { - /* JS etc. */ - if (strict) - return FALSE; /* stop at string */ - else - return TRUE; + if (*s == '\'' && s[1] && s[2] == '\'') { + s += 2; // skip over ':' + } else if (*s == '/' && (s[1] == '*' || s[1] == '/')) { + return false; // stop at comment + } else if (*s == '"') { + // JS etc. + if (strict) { + return false; // stop at string + } else { + return true; + } } } - return FALSE; + return false; } - if (cin_isdefault(s)) - return TRUE; - return FALSE; + if (cin_isdefault(s)) { + return true; + } + return false; } /* @@ -503,23 +512,24 @@ static int cin_isdefault(char_u *s) /* * Recognize a "public/private/protected" scope declaration label. */ -int cin_isscopedecl(char_u *s) +bool cin_isscopedecl(char_u *s) { int i; s = cin_skipcomment(s); - if (STRNCMP(s, "public", 6) == 0) + if (STRNCMP(s, "public", 6) == 0) { i = 6; - else if (STRNCMP(s, "protected", 9) == 0) + } else if (STRNCMP(s, "protected", 9) == 0) { i = 9; - else if (STRNCMP(s, "private", 7) == 0) + } else if (STRNCMP(s, "private", 7) == 0) { i = 7; - else - return FALSE; + } else { + return false; + } return *(s = cin_skipcomment(s + i)) == ':' && s[1] != ':'; } -/* Maximum number of lines to search back for a "namespace" line. */ +// Maximum number of lines to search back for a "namespace" line. #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. @@ -569,12 +579,14 @@ static char_u *after_label(char_u *l) { for (; *l; ++l) { if (*l == ':') { - if (l[1] == ':') /* skip over "::" for C++ */ - ++l; - else if (!cin_iscase(l + 1, FALSE)) + if (l[1] == ':') { // skip over "::" for C++ + l++; + } else if (!cin_iscase(l + 1, false)) { break; - } else if (*l == '\'' && l[1] && l[2] == '\'') - l += 2; /* skip over 'x' */ + } + } else if (*l == '\'' && l[1] && l[2] == '\'') { + l += 2; // skip over 'x' + } } if (*l == NUL) return NULL; @@ -588,10 +600,7 @@ static char_u *after_label(char_u *l) * Get indent of line "lnum", skipping a label. * Return 0 if there is nothing after the label. */ -static int -get_indent_nolabel ( /* XXX */ - linenr_T lnum -) +static int get_indent_nolabel(linenr_T lnum) // XXX { char_u *l; pos_T fp; @@ -624,12 +633,13 @@ static int skip_label(linenr_T lnum, char_u **pp) cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; l = get_cursor_line_ptr(); - /* XXX */ - if (cin_iscase(l, FALSE) || cin_isscopedecl(l) || cin_islabel()) { + // XXX + if (cin_iscase(l, false) || cin_isscopedecl(l) || cin_islabel()) { amount = get_indent_nolabel(lnum); l = after_label(get_cursor_line_ptr()); - if (l == NULL) /* just in case */ + if (l == NULL) { // just in case l = get_cursor_line_ptr(); + } } else { amount = get_indent(); l = get_cursor_line_ptr(); @@ -710,10 +720,11 @@ static int cin_get_equal_amount(linenr_T lnum) line = s = ml_get(lnum); while (*s != NUL && vim_strchr((char_u *)"=;{}\"'", *s) == NULL) { - if (cin_iscomment(s)) /* ignore comments */ + if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); - else - ++s; + } else { + s++; + } } if (*s != '=') return 0; @@ -722,8 +733,9 @@ static int cin_get_equal_amount(linenr_T lnum) if (cin_nocode(s)) return 0; - if (*s == '"') /* nice alignment for continued strings */ - ++s; + if (*s == '"') { // nice alignment for continued strings + s++; + } fp.lnum = lnum; fp.col = (colnr_T)(s - line); @@ -806,8 +818,8 @@ static int cin_islinecomment(char_u *p) static char_u cin_isterminated ( char_u *s, - int incl_open, /* include '{' at the end as terminator */ - int incl_comma /* recognize a trailing comma */ + int incl_open, // include '{' at the end as terminator + int incl_comma // recognize a trailing comma ) { char_u found_start = 0; @@ -823,7 +835,7 @@ cin_isterminated ( is_else = cin_iselse(s); while (*s) { - /* skip over comments, "" strings and 'c'haracters */ + // skip over comments, "" strings and 'c'haracters s = skip_string(cin_skipcomment(s)); if (*s == '}' && n_open > 0) --n_open; @@ -942,12 +954,12 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) s = skipwhite(s); if (!just_started && (!comma && *s != ',' && *s != ')')) break; - just_started = FALSE; - } else if (cin_iscomment(s)) /* ignore comments */ + just_started = false; + } else if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); - else { - ++s; - just_started = FALSE; + } else { + s++; + just_started = false; } } @@ -965,8 +977,9 @@ static int cin_isif(char_u *p) static int cin_iselse(char_u *p) { - if (*p == '}') /* accept "} else" */ + if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); + } return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); } @@ -980,27 +993,24 @@ static int cin_isdo(char_u *p) * We only accept a "while (condition) ;", with only white space between the * ')' and ';'. The condition may be spread over several lines. */ -static int -cin_iswhileofdo ( /* XXX */ - char_u *p, - linenr_T lnum -) +static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; int retval = FALSE; p = cin_skipcomment(p); - if (*p == '}') /* accept "} while (cond);" */ + if (*p == '}') { // accept "} while (cond);" p = cin_skipcomment(p + 1); + } if (cin_starts_with(p, "while")) { cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; p = get_cursor_line_ptr(); - while (*p && *p != 'w') { /* skip any '}', until the 'w' of the "while" */ - ++p; - ++curwin->w_cursor.col; + while (*p && *p != 'w') { // skip any '}', until the 'w' of the "while" + p++; + curwin->w_cursor.col++; } if ((trypos = findmatchlimit(NULL, 0, 0, curbuf->b_ind_maxparen)) != NULL @@ -1067,8 +1077,9 @@ static int cin_iswhileofdo_end(int terminated) pos_T *trypos; int i; - if (terminated != ';') /* there must be a ';' at the end */ - return FALSE; + if (terminated != ';') { // there must be a ';' at the end + return false; + } p = line = get_cursor_line_ptr(); while (*p != NUL) { @@ -1083,15 +1094,16 @@ static int cin_iswhileofdo_end(int terminated) trypos = find_match_paren(curbuf->b_ind_maxparen); if (trypos != NULL) { s = cin_skipcomment(ml_get(trypos->lnum)); - if (*s == '}') /* accept "} while (cond);" */ + if (*s == '}') { // accept "} while (cond);" s = cin_skipcomment(s + 1); + } if (cin_starts_with(s, "while")) { curwin->w_cursor.lnum = trypos->lnum; return TRUE; } } - /* Searching may have made "line" invalid, get it again. */ + // Searching may have made "line" invalid, get it again. line = get_cursor_line_ptr(); p = line + i; } @@ -1134,8 +1146,9 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { pos->col = 0; s = skipwhite(line); - if (*s == '#') /* skip #define FOO x ? (x) : x */ - return FALSE; + if (*s == '#') { // skip #define FOO x ? (x) : x + return false; + } s = cin_skipcomment(s); if (*s == NUL) return FALSE; @@ -1230,23 +1243,23 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { class_or_struct = FALSE; lookfor_ctor_init = TRUE; } else if (s[0] == '?') { - /* Avoid seeing '() :' after '?' as constructor init. */ - return FALSE; + // Avoid seeing '() :' after '?' as constructor init. + return false; } else if (!vim_isIDc(s[0])) { - /* if it is not an identifier, we are wrong */ + // if it is not an identifier, we are wrong class_or_struct = false; lookfor_ctor_init = false; } else if (pos->col == 0) { - /* it can't be a constructor-initialization any more */ - lookfor_ctor_init = FALSE; + // it can't be a constructor-initialization any more + lookfor_ctor_init = false; - /* the first statement starts here: lineup with this one... */ + // the first statement starts here: lineup with this one... if (cpp_base_class) { pos->col = (colnr_T)(s - line); } } - /* When the line ends in a comma don't align with it. */ + // When the line ends in a comma don't align with it. if (lnum == curwin->w_cursor.lnum && *s == ',' && cin_nocode(s + 1)) { pos->col = 0; } @@ -1271,10 +1284,12 @@ static int get_baseclass_amount(int col) if (col == 0) { amount = get_indent(); if (find_last_paren(get_cursor_line_ptr(), '(', ')') - && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) - amount = get_indent_lnum(trypos->lnum); /* XXX */ - if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { + amount = get_indent_lnum(trypos->lnum); // XXX + } + if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) { amount += curbuf->b_ind_cpp_baseclass; + } } else { curwin->w_cursor.col = col; getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); @@ -1389,12 +1404,12 @@ static int cin_skip2pos(pos_T *trypos) * Return NULL if no match found. * Ignore a '{' that is in a comment, makes indenting the next three lines * work. */ -/* foo() */ -/* { */ -/* } */ +// foo() +// { +// } -static pos_T *find_start_brace(void) -{ /* XXX */ +static pos_T *find_start_brace(void) // XXX +{ pos_T cursor_save; pos_T *trypos; pos_T *pos; @@ -1402,11 +1417,11 @@ static pos_T *find_start_brace(void) cursor_save = curwin->w_cursor; while ((trypos = findmatchlimit(NULL, '{', FM_BLOCKSTOP, 0)) != NULL) { - pos_copy = *trypos; /* copy pos_T, next findmatch will change it */ + pos_copy = *trypos; // copy pos_T, next findmatch will change it trypos = &pos_copy; curwin->w_cursor = *trypos; pos = NULL; - /* ignore the { if it's in a // or / * * / comment */ + // ignore the { if it's in a // or / * * / comment if ((colnr_T)cin_skip2pos(trypos) == trypos->col && (pos = ind_find_start_CORS(NULL)) == NULL) { // XXX break; @@ -1449,7 +1464,7 @@ retry: } else { pos_T *trypos_wk; - pos_copy = *trypos; /* copy trypos, findmatch will change it */ + pos_copy = *trypos; // copy trypos, findmatch will change it trypos = &pos_copy; curwin->w_cursor = *trypos; if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) { // XXX @@ -1515,17 +1530,17 @@ static int find_last_paren(char_u *l, int start, int end) int retval = FALSE; int open_count = 0; - curwin->w_cursor.col = 0; /* default is start of line */ + curwin->w_cursor.col = 0; // default is start of line for (i = 0; l[i] != NUL; i++) { - i = (int)(cin_skipcomment(l + i) - l); /* ignore parens in comments */ - i = (int)(skip_string(l + i) - l); /* ignore parens in quotes */ - if (l[i] == start) - ++open_count; - else if (l[i] == end) { - if (open_count > 0) - --open_count; - else { + i = (int)(cin_skipcomment(l + i) - l); // ignore parens in comments + i = (int)(skip_string(l + i) - l); // ignore parens in quotes + if (l[i] == start) { + open_count++; + } else if (l[i] == end) { + if (open_count > 0) { + open_count--; + } else { curwin->w_cursor.col = i; retval = TRUE; } @@ -1561,7 +1576,7 @@ void parse_cino(buf_T *buf) * an opening brace. */ buf->b_ind_no_brace = 0; - /* Column where the first { of a function should be located }. */ + // Column where the first { of a function should be located }. buf->b_ind_first_open = 0; /* Spaces from the prevailing indent a leftmost open brace should be @@ -1581,26 +1596,26 @@ void parse_cino(buf_T *buf) * otherwise the jump label will be put to column 1. */ buf->b_ind_jump_label = -1; - /* Spaces from the switch() indent a "case xx" label should be located. */ + // Spaces from the switch() indent a "case xx" label should be located. buf->b_ind_case = sw; - /* Spaces from the "case xx:" code after a switch() should be located. */ + // Spaces from the "case xx:" code after a switch() should be located. buf->b_ind_case_code = sw; - /* Lineup break at end of case in switch() with case label. */ + // Lineup break at end of case in switch() with case label. buf->b_ind_case_break = 0; /* Spaces from the class declaration indent a scope declaration label * should be located. */ buf->b_ind_scopedecl = sw; - /* Spaces from the scope declaration label code should be located. */ + // Spaces from the scope declaration label code should be located. buf->b_ind_scopedecl_code = sw; - /* Amount K&R-style parameters should be indented. */ + // Amount K&R-style parameters should be indented. buf->b_ind_param = sw; - /* Amount a function type spec should be indented. */ + // Amount a function type spec should be indented. buf->b_ind_func_type = sw; /* Amount a cpp base class declaration or constructor initialization @@ -1611,7 +1626,7 @@ void parse_cino(buf_T *buf) * should be located. */ buf->b_ind_continuation = sw; - /* Spaces from the indent of the line with an unclosed parentheses. */ + // Spaces from the indent of the line with an unclosed parentheses. buf->b_ind_unclosed = sw * 2; /* Spaces from the indent of the line with an unclosed parentheses, which @@ -1635,35 +1650,35 @@ void parse_cino(buf_T *buf) * opening parentheses. */ buf->b_ind_matching_paren = 0; - /* Indent a closing parentheses under the previous line. */ + // Indent a closing parentheses under the previous line. buf->b_ind_paren_prev = 0; - /* Extra indent for comments. */ + // Extra indent for comments. buf->b_ind_comment = 0; - /* Spaces from the comment opener when there is nothing after it. */ + // Spaces from the comment opener when there is nothing after it. buf->b_ind_in_comment = 3; /* Boolean: if non-zero, use b_ind_in_comment even if there is something * after the comment opener. */ buf->b_ind_in_comment2 = 0; - /* Max lines to search for an open paren. */ + // Max lines to search for an open paren. buf->b_ind_maxparen = 20; - /* Max lines to search for an open comment. */ + // Max lines to search for an open comment. buf->b_ind_maxcomment = 70; - /* Handle braces for java code. */ + // Handle braces for java code. buf->b_ind_java = 0; - /* Not to confuse JS object properties with labels. */ + // Not to confuse JS object properties with labels. buf->b_ind_js = 0; - /* Handle blocked cases correctly. */ + // Handle blocked cases correctly. buf->b_ind_keep_case_label = 0; - /* Handle C++ namespace. */ + // Handle C++ namespace. buf->b_ind_cpp_namespace = 0; /* Handle continuation lines containing conditions of if(), for() and @@ -1777,9 +1792,9 @@ int get_c_indent(void) pos_T our_paren_pos; char_u *start; int start_brace; -#define BRACE_IN_COL0 1 /* '{' is in column 0 */ -#define BRACE_AT_START 2 /* '{' is at start of line */ -#define BRACE_AT_END 3 /* '{' is at end of line */ +#define BRACE_IN_COL0 1 // '{' is in column 0 +#define BRACE_AT_START 2 // '{' is at start of line +#define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; char_u *l; char_u *look; @@ -1802,24 +1817,24 @@ int get_c_indent(void) int whilelevel; linenr_T lnum; int n; - int iscase; int lookfor_break; - int lookfor_cpp_namespace = FALSE; - int cont_amount = 0; /* amount for continuation line */ + bool lookfor_cpp_namespace = false; + int cont_amount = 0; // amount for continuation line int original_line_islabel; int added_to_amount = 0; linenr_T raw_string_start = 0; cpp_baseclass_cache_T cache_cpp_baseclass = { false, { MAXLNUM, 0 } }; - /* make a copy, value is changed below */ + // make a copy, value is changed below int ind_continuation = curbuf->b_ind_continuation; - /* remember where the cursor was when we started */ + // remember where the cursor was when we started cur_curpos = curwin->w_cursor; - /* if we are at line 1 zero indent is fine, right? */ - if (cur_curpos.lnum == 1) + // if we are at line 1 zero indent is fine, right? + if (cur_curpos.lnum == 1) { return 0; + } /* Get a copy of the current contents of the line. * This is required, because only the most recent line obtained with @@ -1840,11 +1855,11 @@ int get_c_indent(void) theline = skipwhite(linecopy); - /* move the cursor to the start of the line */ + // move the cursor to the start of the line curwin->w_cursor.col = 0; - original_line_islabel = cin_islabel(); /* XXX */ + original_line_islabel = cin_islabel(); // XXX /* * If we are inside a raw string don't change the indent. @@ -1852,7 +1867,7 @@ int get_c_indent(void) */ comment_pos = ind_find_start_comment(); if (comment_pos != NULL) { - /* findmatchlimit() static pos is overwritten, make a copy */ + // findmatchlimit() static pos is overwritten, make a copy tryposCopy = *comment_pos; comment_pos = &tryposCopy; } @@ -1887,8 +1902,8 @@ int get_c_indent(void) * previous line, lineup with that one. */ if (cin_islinecomment(theline) - && (trypos = find_line_comment()) != NULL) { /* XXX */ - /* find how indented the line beginning the comment is */ + && (trypos = find_line_comment()) != NULL) { // XXX + // find how indented the line beginning the comment is getvcol(curwin, trypos, &col, NULL, NULL); amount = col; goto theend; @@ -1897,18 +1912,18 @@ int get_c_indent(void) * If we're inside a comment and not looking at the start of the * comment, try using the 'comments' option. */ - if (!cin_iscomment(theline) && comment_pos != NULL) { /* XXX */ + if (!cin_iscomment(theline) && comment_pos != NULL) { // XXX int lead_start_len = 2; int lead_middle_len = 1; - char_u lead_start[COM_MAX_LEN]; /* start-comment string */ - char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */ - char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + char_u lead_start[COM_MAX_LEN]; // start-comment string + char_u lead_middle[COM_MAX_LEN]; // middle-comment string + char_u lead_end[COM_MAX_LEN]; // end-comment string char_u *p; int start_align = 0; int start_off = 0; int done = FALSE; - /* find how indented the line beginning the comment is */ + // find how indented the line beginning the comment is getvcol(curwin, comment_pos, &col, NULL, NULL); amount = col; *lead_start = NUL; @@ -1981,13 +1996,13 @@ int get_c_indent(void) if (STRNCMP(theline, lead_middle, lead_middle_len) != 0 && STRNCMP(theline, lead_end, STRLEN(lead_end)) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); - /* XXX */ - if (off != 0) + // XXX + if (off != 0) { amount += off; - else if (align == COM_RIGHT) - amount += vim_strsize(lead_start) - - vim_strsize(lead_middle); - done = TRUE; + } else if (align == COM_RIGHT) { + amount += vim_strsize(lead_start) - vim_strsize(lead_middle); + } + done = true; break; } } @@ -2010,18 +2025,20 @@ int get_c_indent(void) * otherwise, add the amount specified by "c" in 'cino' */ amount = -1; - for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; --lnum) { - if (linewhite(lnum)) /* skip blank lines */ + for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; lnum--) { + if (linewhite(lnum)) { // skip blank lines continue; - amount = get_indent_lnum(lnum); /* XXX */ + } + amount = get_indent_lnum(lnum); // XXX break; } - if (amount == -1) { /* use the comment opener */ + if (amount == -1) { // use the comment opener if (!curbuf->b_ind_in_comment2) { - start = ml_get(comment_pos->lnum); - look = start + comment_pos->col + 2; /* skip / and * */ - if (*look != NUL) /* if something after it */ - comment_pos->col = (colnr_T)(skipwhite(look) - start); + start = ml_get(comment_pos->lnum); + look = start + comment_pos->col + 2; // skip / and * + if (*look != NUL) { // if something after it + comment_pos->col = (colnr_T)(skipwhite(look) - start); + } } getvcol(curwin, comment_pos, &col, NULL, NULL); amount = col; @@ -2038,9 +2055,8 @@ int get_c_indent(void) amount = get_indent_lnum(trypos->lnum); goto theend; } - /* - * Are we inside parentheses or braces? - */ /* XXX */ + // Are we inside parentheses or braces? + // XXX if (((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL && curbuf->b_ind_java == 0) || (tryposBrace = find_start_brace()) != NULL @@ -2063,8 +2079,8 @@ int get_c_indent(void) * a previous non-empty line that matches the same paren. */ if (theline[0] == ')' && curbuf->b_ind_paren_prev) { - /* Line up with the start of the matching paren line. */ - amount = get_indent_lnum(curwin->w_cursor.lnum - 1); /* XXX */ + // Line up with the start of the matching paren line. + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX } else { amount = -1; for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) { @@ -2083,12 +2099,12 @@ int get_c_indent(void) continue; } - /* XXX */ + // XXX if ((trypos = find_match_paren( corr_ind_maxparen(&cur_curpos))) != NULL && trypos->lnum == our_paren_pos.lnum && trypos->col == our_paren_pos.col) { - amount = get_indent_lnum(lnum); /* XXX */ + amount = get_indent_lnum(lnum); // XXX if (theline[0] == ')') { if (our_paren_pos.lnum != lnum @@ -2200,10 +2216,11 @@ int get_c_indent(void) col = our_paren_pos.col + 1; while (ascii_iswhite(l[col])) col++; - if (l[col] != NUL) /* In case of trailing space */ + if (l[col] != NUL) { // In case of trailing space our_paren_pos.col = col; - else + } else { our_paren_pos.col++; + } } } @@ -2219,7 +2236,7 @@ int get_c_indent(void) } if (theline[0] == ')' && curbuf->b_ind_matching_paren) { - /* Line up with the start of the matching paren line. */ + // Line up with the start of the matching paren line. } else if ((curbuf->b_ind_unclosed == 0 && is_if_for_while == 0) || (!curbuf->b_ind_unclosed_noignore && *look == '(' && ignore_paren_col == 0)) { @@ -2271,9 +2288,10 @@ int get_c_indent(void) } } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; + } } else { // We are inside braces, there is a { before this line at the position // stored in tryposBrace. @@ -2317,7 +2335,7 @@ int get_c_indent(void) // ldfd) { // } if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) - && cin_iscase(skipwhite(get_cursor_line_ptr()), FALSE)) { + && cin_iscase(skipwhite(get_cursor_line_ptr()), false)) { amount = get_indent(); } else if (curbuf->b_ind_js) { amount = get_indent_lnum(lnum); @@ -2348,14 +2366,15 @@ int get_c_indent(void) * to match it with. */ lookfor = LOOKFOR_INITIAL; - if (cin_iselse(theline)) + if (cin_iselse(theline)) { lookfor = LOOKFOR_IF; - else if (cin_iswhileofdo(theline, cur_curpos.lnum)) /* XXX */ + } else if (cin_iswhileofdo(theline, cur_curpos.lnum)) { // XXX lookfor = LOOKFOR_DO; + } if (lookfor != LOOKFOR_INITIAL) { curwin->w_cursor.lnum = cur_curpos.lnum; if (find_match(lookfor, ourscope) == OK) { - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX goto theend; } } @@ -2390,7 +2409,7 @@ int get_c_indent(void) amount += curbuf->b_ind_cpp_extern_c; } } else { - /* Compensate for adding b_ind_open_extra later. */ + // Compensate for adding b_ind_open_extra later. amount -= curbuf->b_ind_open_extra; if (amount < 0) amount = 0; @@ -2399,19 +2418,20 @@ int get_c_indent(void) lookfor_break = FALSE; - if (cin_iscase(theline, FALSE)) { /* it's a switch() label */ - lookfor = LOOKFOR_CASE; /* find a previous switch() label */ + if (cin_iscase(theline, false)) { // it's a switch() label + lookfor = LOOKFOR_CASE; // find a previous switch() label amount += curbuf->b_ind_case; - } else if (cin_isscopedecl(theline)) { /* private:, ... */ - lookfor = LOOKFOR_SCOPEDECL; /* class decl is this block */ + } else if (cin_isscopedecl(theline)) { // private:, ... + lookfor = LOOKFOR_SCOPEDECL; // class decl is this block amount += curbuf->b_ind_scopedecl; } else { - if (curbuf->b_ind_case_break && cin_isbreak(theline)) - /* break; ... */ - lookfor_break = TRUE; + if (curbuf->b_ind_case_break && cin_isbreak(theline)) { + // break; ... + lookfor_break = true; + } lookfor = LOOKFOR_INITIAL; - /* b_ind_level from start of block */ + // b_ind_level from start of block amount += curbuf->b_ind_level; } scope_amount = amount; @@ -2503,16 +2523,17 @@ int get_c_indent(void) if (terminated != ';' && cin_isinit()) break; - /* nothing useful found */ - if (terminated == 0 || terminated == '{') + // nothing useful found + if (terminated == 0 || terminated == '{') { continue; + } } if (terminated != ';') { - /* Skip parens and braces. Position the cursor - * over the rightmost paren, so that matching it - * will take us back to the start of the line. - */ /* XXX */ + // Skip parens and braces. Position the cursor + // over the rightmost paren, so that matching it + // will take us back to the start of the line. + // XXX trypos = NULL; if (find_last_paren(l, '(', ')')) trypos = find_match_paren( @@ -2582,7 +2603,7 @@ int get_c_indent(void) continue; } - /* Finally the actual check for "namespace". */ + // Finally the actual check for "namespace". if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace - added_to_amount; @@ -2614,7 +2635,7 @@ int get_c_indent(void) * If this is a switch() label, may line up relative to that. * If this is a C++ scope declaration, do the same. */ - iscase = cin_iscase(l, FALSE); + bool iscase = cin_iscase(l, false); if (iscase || cin_isscopedecl(l)) { /* we are only looking for cpp base class * declaration/initialization any longer */ @@ -2640,27 +2661,24 @@ int get_c_indent(void) break; } - /* - * case xx: <- line up with this case - * x = 333; - * case yy: - */ - if ( (iscase && lookfor == LOOKFOR_CASE) - || (iscase && lookfor_break) - || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) { - /* - * Check that this case label is not for another - * switch() - */ /* XXX */ + // case xx: <- line up with this case + // x = 333; + // case yy: + if ((iscase && lookfor == LOOKFOR_CASE) + || (iscase && lookfor_break) + || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) { + // Check that this case label is not for another + // switch() + // XXX if ((trypos = find_start_brace()) == NULL || trypos->lnum == ourscope) { - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX break; } continue; } - n = get_indent_nolabel(curwin->w_cursor.lnum); /* XXX */ + n = get_indent_nolabel(curwin->w_cursor.lnum); // XXX /* * case xx: if (cond) <- line up with this if @@ -2708,7 +2726,7 @@ int get_c_indent(void) * case xx: * -> y = 1; */ - scope_amount = get_indent() + (iscase /* XXX */ + scope_amount = get_indent() + (iscase // XXX ? curbuf->b_ind_case_code : curbuf->b_ind_scopedecl_code); lookfor = curbuf->b_ind_case_break @@ -2750,11 +2768,10 @@ int get_c_indent(void) continue; } - /* - * Are we at the start of a cpp base class declaration or - * constructor initialization? - */ /* XXX */ - n = FALSE; + // Are we at the start of a cpp base class declaration or + // constructor initialization? + // XXX + n = 0; if (lookfor != LOOKFOR_TERM && curbuf->b_ind_cpp_baseclass > 0) { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); l = get_cursor_line_ptr(); @@ -2766,13 +2783,14 @@ int get_c_indent(void) else amount += ind_continuation; } else if (theline[0] == '{') { - /* Need to find start of the declaration. */ + // Need to find start of the declaration. lookfor = LOOKFOR_UNTERM; ind_continuation = 0; continue; - } else - /* XXX */ + } else { + // XXX amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col); + } break; } else if (lookfor == LOOKFOR_CPP_BASECLASS) { /* only look, whether there is a cpp base class @@ -2871,8 +2889,8 @@ int get_c_indent(void) */ curwin->w_cursor = *trypos; l = get_cursor_line_ptr(); - if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) { - ++curwin->w_cursor.lnum; + if (cin_iscase(l, false) || cin_isscopedecl(l)) { + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; continue; } @@ -3025,9 +3043,10 @@ int get_c_indent(void) * -> here; */ if (lookfor == LOOKFOR_UNTERM) { - /* When line ends in a comma add extra indent */ - if (terminated == ',') + // When line ends in a comma add extra indent + if (terminated == ',') { amount += ind_continuation; + } break; } @@ -3144,9 +3163,10 @@ int get_c_indent(void) if (whilelevel == 0) { lookfor = LOOKFOR_TERM; - amount = get_indent(); /* XXX */ - if (theline[0] == '{') + amount = get_indent(); // XXX + if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; + } } ++whilelevel; } @@ -3174,8 +3194,8 @@ int get_c_indent(void) if (whilelevel > 0) { l = cin_skipcomment(get_cursor_line_ptr()); if (cin_isdo(l)) { - amount = get_indent(); /* XXX */ - --whilelevel; + amount = get_indent(); // XXX + whilelevel--; continue; } } @@ -3240,8 +3260,8 @@ term_again: */ curwin->w_cursor = *trypos; l = get_cursor_line_ptr(); - if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) { - ++curwin->w_cursor.lnum; + if (cin_iscase(l, false) || cin_isscopedecl(l)) { + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; continue; } @@ -3256,8 +3276,7 @@ term_again: * stat; * } */ - iscase = (curbuf->b_ind_keep_case_label - && cin_iscase(l, FALSE)); + iscase = curbuf->b_ind_keep_case_label && cin_iscase(l, false); /* * Get indent and pointer to text for current line, @@ -3267,7 +3286,7 @@ term_again: if (theline[0] == '{') amount += curbuf->b_ind_open_extra; - /* See remark above: "Only add b_ind_open_extra.." */ + // See remark above: "Only add b_ind_open_extra.." l = skipwhite(l); if (*l == '{') amount -= curbuf->b_ind_open_extra; @@ -3297,11 +3316,11 @@ term_again: * that block. */ l = get_cursor_line_ptr(); - if (find_last_paren(l, '{', '}') /* XXX */ + if (find_last_paren(l, '{', '}') // XXX && (trypos = find_start_brace()) != NULL) { curwin->w_cursor = *trypos; - /* if not "else {" check for terminated again */ - /* but skip block for "} else {" */ + // if not "else {" check for terminated again + // but skip block for "} else {" l = cin_skipcomment(get_cursor_line_ptr()); if (*l == '}' || !cin_iselse(l)) goto term_again; @@ -3314,13 +3333,14 @@ term_again: } } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; - - /* subtract extra left-shift for jump labels */ - if (curbuf->b_ind_jump_label > 0 && original_line_islabel) + } + // subtract extra left-shift for jump labels + if (curbuf->b_ind_jump_label > 0 && original_line_islabel) { amount -= curbuf->b_ind_jump_label; + } goto theend; } @@ -3360,7 +3380,7 @@ term_again: goto theend; } - /* search backwards until we find something we recognize */ + // search backwards until we find something we recognize amount = 0; curwin->w_cursor = cur_curpos; while (curwin->w_cursor.lnum > 1) { @@ -3386,7 +3406,7 @@ term_again: l = get_cursor_line_ptr(); } if (n) { - /* XXX */ + // XXX amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col); break; } @@ -3415,11 +3435,11 @@ term_again: */ if (cin_ends_in(l, (char_u *)",", NULL) || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { - /* take us back to opening paren */ + // take us back to opening paren if (find_last_paren(l, '(', ')') - && (trypos = find_match_paren( - curbuf->b_ind_maxparen)) != NULL) + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { curwin->w_cursor = *trypos; + } /* For a line ending in ',' that is a continuation line go * back to the first line with a backslash: @@ -3435,7 +3455,7 @@ term_again: curwin->w_cursor.col = 0; } - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX if (amount == 0) amount = cin_first_id_amount(); @@ -3448,8 +3468,9 @@ term_again: * If the line looks like a function declaration, and we're * not in a comment, put it the left margin. */ - if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) /* XXX */ + if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) { // XXX break; + } l = get_cursor_line_ptr(); /* @@ -3535,13 +3556,14 @@ term_again: if ((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) curwin->w_cursor = *trypos; - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX break; } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; + } /* add extra indent if the previous line ended in a backslash: * "asdfasdf\ @@ -3565,7 +3587,7 @@ theend: amount = 0; laterend: - /* put the cursor back where it belongs */ + // put the cursor back where it belongs curwin->w_cursor = cur_curpos; xfree(linecopy); @@ -3598,7 +3620,7 @@ static int find_match(int lookfor, linenr_T ourscope) look = cin_skipcomment(get_cursor_line_ptr()); if (!cin_iselse(look) && !cin_isif(look) - && !cin_isdo(look) /* XXX */ + && !cin_isdo(look) // XXX && !cin_iswhileofdo(look, curwin->w_cursor.lnum)) { continue; } @@ -3607,9 +3629,10 @@ static int find_match(int lookfor, linenr_T ourscope) * if we've gone outside the braces entirely, * we must be out of scope... */ - theirscope = find_start_brace(); /* XXX */ - if (theirscope == NULL) + theirscope = find_start_brace(); // XXX + if (theirscope == NULL) { break; + } /* * and if the brace enclosing this is further @@ -3649,7 +3672,7 @@ static int find_match(int lookfor, linenr_T ourscope) continue; } - /* If it's an "if" decrement elselevel */ + // If it's an "if" decrement elselevel look = cin_skipcomment(get_cursor_line_ptr()); if (cin_isif(look)) { elselevel--; @@ -3661,9 +3684,10 @@ static int find_match(int lookfor, linenr_T ourscope) whilelevel = 0; } - /* If it's a "do" decrement whilelevel */ - if (cin_isdo(look)) + // If it's a "do" decrement whilelevel + if (cin_isdo(look)) { whilelevel--; + } /* * if we've used up all the elses, then diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h index ab9270081e..452998a5a4 100644 --- a/src/nvim/lib/queue.h +++ b/src/nvim/lib/queue.h @@ -33,11 +33,17 @@ typedef struct _queue { #define QUEUE_DATA(ptr, type, field) \ ((type *)((char *)(ptr) - offsetof(type, field))) -// Important note: mutating the list while QUEUE_FOREACH is -// iterating over its elements results in undefined behavior. -#define QUEUE_FOREACH(q, h) \ - for ( /* NOLINT(readability/braces) */ \ - (q) = (h)->next; (q) != (h); (q) = (q)->next) +// Important note: the node currently being processed can be safely deleted. +// otherwise, mutating the list while QUEUE_FOREACH is iterating over its +// elements results in undefined behavior. +#define QUEUE_FOREACH(q, h, code) \ + (q) = (h)->next; \ + while((q) != (h)) { \ + QUEUE *next = q->next; \ + code \ + (q) = next; \ + } + // ffi.cdef is unable to swallow `bool` in place of `int` here. static inline int QUEUE_EMPTY(const QUEUE *const q) diff --git a/src/nvim/log.h b/src/nvim/log.h index f2e74df031..17d754c033 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -17,10 +17,11 @@ #endif -#define DEBUG_LOG_LEVEL 0 -#define INFO_LOG_LEVEL 1 -#define WARN_LOG_LEVEL 2 -#define ERROR_LOG_LEVEL 3 +#define TRACE_LOG_LEVEL 0 +#define DEBUG_LOG_LEVEL 1 +#define INFO_LOG_LEVEL 2 +#define WARN_LOG_LEVEL 3 +#define ERROR_LOG_LEVEL 4 #define DLOG(...) #define DLOGN(...) diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 030df69caa..ce8c9b0d06 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: @@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end: case LUA_TFUNCTION: { LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = LUA_NOREF; char_u *name = register_cfunc( &nlua_CFunction_func_call, @@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end: break; } case LUA_TUSERDATA: { + // TODO(bfredl): check mt.__call and convert to function? nlua_pushref(lstate, nlua_nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); @@ -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/converter.h b/src/nvim/lua/converter.h index 8601a32418..43a7e06019 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -11,7 +11,6 @@ typedef struct { LuaRef func_ref; - LuaRef table_ref; } LuaCallable; typedef struct { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 3219c02068..f99a2dd0fe 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/assert.h" #include "nvim/version.h" #include "nvim/misc1.h" #include "nvim/getchar.h" @@ -16,8 +17,10 @@ #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" +#include "nvim/extmark.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" +#include "nvim/map.h" #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" @@ -32,9 +35,7 @@ #include "nvim/event/time.h" #include "nvim/event/loop.h" -#ifdef WIN32 #include "nvim/os/os.h" -#endif #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" @@ -63,6 +64,11 @@ typedef struct { } \ } +#if __has_feature(address_sanitizer) + PMap(handle_T) *nlua_ref_markers = NULL; +# define NLUA_TRACK_REFS +#endif + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -547,6 +553,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static lua_State *nlua_init(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { +#ifdef NLUA_TRACK_REFS + const char *env = os_getenv("NVIM_LUA_NOTRACK"); + if (!env || !*env) { + nlua_ref_markers = pmap_new(handle_T)(); + } +#endif + lua_State *lstate = luaL_newstate(); if (lstate == NULL) { EMSG(_("E970: Failed to initialize lua interpreter")); @@ -554,9 +567,13 @@ static lua_State *nlua_init(void) } luaL_openlibs(lstate); nlua_state_init(lstate); + return lstate; } +// only to be used by nlua_enter and nlua_free_all_mem! +static lua_State *global_lstate = NULL; + /// Enter lua interpreter /// /// Calls nlua_init() if needed. Is responsible for pre-lua call initalization @@ -567,26 +584,39 @@ static lua_State *nlua_init(void) static lua_State *nlua_enter(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - static lua_State *global_lstate = NULL; if (global_lstate == NULL) { global_lstate = nlua_init(); } lua_State *const lstate = global_lstate; - // Last used p_rtp value. Must not be dereferenced because value pointed to - // may already be freed. Used to check whether &runtimepath option value - // changed. - static const void *last_p_rtp = NULL; - if (last_p_rtp != (const void *)p_rtp) { - // stack: (empty) - lua_getglobal(lstate, "vim"); - // stack: vim - lua_pop(lstate, 1); - // stack: (empty) - last_p_rtp = (const void *)p_rtp; - } return lstate; } +void nlua_free_all_mem(void) +{ + if (!global_lstate) { + return; + } + lua_State *lstate = global_lstate; + + nlua_unref(lstate, nlua_nil_ref); + nlua_unref(lstate, nlua_empty_dict_ref); + +#ifdef NLUA_TRACK_REFS + if (nlua_refcount) { + fprintf(stderr, "%d lua references were leaked!", nlua_refcount); + } + + if (nlua_ref_markers) { + // in case there are leaked luarefs, leak the associated memory + // to get LeakSanitizer stacktraces on exit + pmap_free(handle_T)(nlua_ref_markers); + } +#endif + + nlua_refcount = 0; + lua_close(lstate); +} + static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -866,17 +896,35 @@ static int nlua_getenv(lua_State *lstate) } #endif + /// add the value to the registry LuaRef nlua_ref(lua_State *lstate, int index) { lua_pushvalue(lstate, index); - return luaL_ref(lstate, LUA_REGISTRYINDEX); + LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX); + if (ref > 0) { + nlua_refcount++; +#ifdef NLUA_TRACK_REFS + if (nlua_ref_markers) { + // dummy allocation to make LeakSanitizer track our luarefs + pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3)); + } +#endif + } + return ref; } /// remove the value from the registry void nlua_unref(lua_State *lstate, LuaRef ref) { if (ref > 0) { + nlua_refcount--; +#ifdef NLUA_TRACK_REFS + // NB: don't remove entry from map to track double-unref + if (nlua_ref_markers) { + xfree(pmap_get(handle_T)(nlua_ref_markers, ref)); + } +#endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); } } @@ -893,19 +941,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref) lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } + /// Gets a new reference to an object stored at original_ref /// /// NOTE: It does not copy the value, it creates a new ref to the lua object. /// Leaves the stack unchanged. -LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) -{ - nlua_pushref(lstate, original_ref); - LuaRef new_ref = nlua_ref(lstate, -1); - lua_pop(lstate, 1); - - return new_ref; -} - LuaRef api_new_luaref(LuaRef original_ref) { if (original_ref == LUA_NOREF) { @@ -913,7 +953,10 @@ LuaRef api_new_luaref(LuaRef original_ref) } lua_State *const lstate = nlua_enter(); - return nlua_newref(lstate, original_ref); + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + return new_ref; } @@ -1023,25 +1066,13 @@ int typval_exec_lua_callable( typval_T *rettv ) { - int offset = 0; LuaRef cb = lua_cb.func_ref; - if (cb == LUA_NOREF) { - // This shouldn't happen. - luaL_error(lstate, "Invalid function passed to VimL"); - return ERROR_OTHER; - } - nlua_pushref(lstate, cb); - if (lua_cb.table_ref != LUA_NOREF) { - offset += 1; - nlua_pushref(lstate, lua_cb.table_ref); - } - PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); - if (lua_pcall(lstate, argcount + offset, 1, 0)) { + if (lua_pcall(lstate, argcount, 1, 0)) { nlua_print(lstate); return ERROR_OTHER; } @@ -1213,13 +1244,16 @@ void ex_luado(exarg_T *const eap) break; } lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + const char *old_line = (const char *)ml_get_buf(curbuf, l, false); + lua_pushstring(lstate, old_line); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { + size_t old_line_len = STRLEN(old_line); + size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); char *const new_line_transformed = xmemdupz(new_line, new_line_len); @@ -1229,7 +1263,7 @@ void ex_luado(exarg_T *const eap) } } ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); + inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len); } lua_pop(lstate, 1); } @@ -1272,6 +1306,12 @@ bool nlua_exec_file(const char *path) return true; } +int tslua_get_language_version(lua_State *L) +{ + lua_pushnumber(L, TREE_SITTER_LANGUAGE_VERSION); + return 1; +} + static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); @@ -1288,8 +1328,85 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); - lua_pushcfunction(lstate, ts_lua_parse_query); + lua_pushcfunction(lstate, tslua_parse_query); lua_setfield(lstate, -2, "_ts_parse_query"); + + lua_pushcfunction(lstate, tslua_get_language_version); + lua_setfield(lstate, -2, "_ts_get_language_version"); +} + +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) @@ -1425,6 +1542,8 @@ static int regex_match_line(lua_State *lstate) return nret; } +// Required functions for lua c functions as VimL callbacks + int nlua_CFunction_func_call( int argcount, typval_T *argvars, @@ -1434,53 +1553,40 @@ int nlua_CFunction_func_call( lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - return typval_exec_lua_callable( - lstate, - funcstate->lua_callable, - argcount, - argvars, - rettv); + return typval_exec_lua_callable(lstate, funcstate->lua_callable, + argcount, argvars, rettv); } -/// Required functions for lua c functions as VimL callbacks + void nlua_CFunction_func_free(void *state) { lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; nlua_unref(lstate, funcstate->lua_callable.func_ref); - nlua_unref(lstate, funcstate->lua_callable.table_ref); xfree(funcstate); } bool nlua_is_table_from_lua(typval_T *const arg) { - if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { - return false; - } - if (arg->v_type == VAR_DICT) { - return arg->vval.v_dict->lua_table_ref > 0 - && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + return arg->vval.v_dict->lua_table_ref != LUA_NOREF; } else if (arg->v_type == VAR_LIST) { - return arg->vval.v_list->lua_table_ref > 0 - && arg->vval.v_list->lua_table_ref != LUA_NOREF; + return arg->vval.v_list->lua_table_ref != LUA_NOREF; + } else { + return false; } - - return false; } char_u *nlua_register_table_as_callable(typval_T *const arg) { - if (!nlua_is_table_from_lua(arg)) { - return NULL; - } - - LuaRef table_ref; + LuaRef table_ref = LUA_NOREF; if (arg->v_type == VAR_DICT) { table_ref = arg->vval.v_dict->lua_table_ref; } else if (arg->v_type == VAR_LIST) { table_ref = arg->vval.v_list->lua_table_ref; - } else { + } + + if (table_ref == LUA_NOREF) { return NULL; } @@ -1490,55 +1596,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) int top = lua_gettop(lstate); #endif - nlua_pushref(lstate, table_ref); + nlua_pushref(lstate, table_ref); // [table] if (!lua_getmetatable(lstate, -1)) { + lua_pop(lstate, 1); + assert(top == lua_gettop(lstate)); return NULL; - } + } // [table, mt] - lua_getfield(lstate, -1, "__call"); + lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call] if (!lua_isfunction(lstate, -1)) { + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); return NULL; } - - LuaRef new_table_ref = nlua_newref(lstate, table_ref); + lua_pop(lstate, 2); // [table] LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = new_table_ref; - char_u *name = register_cfunc( - &nlua_CFunction_func_call, - &nlua_CFunction_func_free, - state); + char_u *name = register_cfunc(&nlua_CFunction_func_call, + &nlua_CFunction_func_free, state); - lua_pop(lstate, 3); + lua_pop(lstate, 1); // [] assert(top == lua_gettop(lstate)); return name; } -/// Helper function to free a list_T -void nlua_free_typval_list(list_T *const l) -{ - if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, l->lua_table_ref); - l->lua_table_ref = LUA_NOREF; - } -} - - -/// Helper function to free a dict_T -void nlua_free_typval_dict(dict_T *const d) -{ - if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, d->lua_table_ref); - d->lua_table_ref = LUA_NOREF; - } -} - void nlua_execute_log_keystroke(int c) { char_u buf[NUMBUFLEN]; diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 1d7a15d9aa..ea774ac2e3 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); +EXTERN int nlua_refcount INIT(= 0); + #define set_api_error(s, err) \ do { \ Error *err_ = (err); \ diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a640b97d3b..c186928ae2 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -171,7 +171,7 @@ int tslua_add_language(lua_State *L) TSLanguage *lang = lang_parser(); if (lang == NULL) { - return luaL_error(L, "Failed to load parser: internal error"); + return luaL_error(L, "Failed to load parser %s: internal error", path); } uint32_t lang_version = ts_language_version(lang); @@ -179,7 +179,8 @@ int tslua_add_language(lua_State *L) || lang_version > TREE_SITTER_LANGUAGE_VERSION) { return luaL_error( L, - "ABI version mismatch : supported between %d and %d, found %d", + "ABI version mismatch for %s: supported between %d and %d, found %d", + path, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, TREE_SITTER_LANGUAGE_VERSION, lang_version); } @@ -221,13 +222,19 @@ int tslua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] size_t nfields = (size_t)ts_language_field_count(lang); - lua_createtable(L, nfields-1, 1); // [retval, fields] - for (size_t i = 0; i < nfields; i++) { + lua_createtable(L, nfields, 1); // [retval, fields] + // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL) + for (size_t i = 1; i <= nfields; i++) { lua_pushstring(L, ts_language_field_name_for_id(lang, i)); lua_rawseti(L, -2, i); // [retval, fields] } lua_setfield(L, -2, "fields"); // [retval] + + uint32_t lang_version = ts_language_version(lang); + lua_pushinteger(L, lang_version); // [retval, version] + lua_setfield(L, -2, "_abi_version"); + return 1; } @@ -1108,7 +1115,7 @@ static int querycursor_gc(lua_State *L) // Query methods -int ts_lua_parse_query(lua_State *L) +int tslua_parse_query(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 80b311de2c..eb54ff28ee 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -39,6 +39,16 @@ assert(vim) vim.inspect = package.loaded['vim.inspect'] assert(vim.inspect) +vim.log = { + levels = { + TRACE = 0; + DEBUG = 1; + INFO = 2; + WARN = 3; + ERROR = 4; + } +} + -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} @@ -253,8 +263,15 @@ end -- vim.fn.{func}(...) vim.fn = setmetatable({}, { __index = function(t, key) - local function _fn(...) - return vim.call(key, ...) + local _fn + if vim.api[key] ~= nil then + _fn = function() + error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key)) + end + else + _fn = function(...) + return vim.call(key, ...) + end end t[key] = _fn return _fn @@ -478,6 +495,23 @@ function vim.defer_fn(fn, timeout) return timer end + +--- Notification provider +--- without a runtime, writes to :Messages +-- see :help nvim_notify +--@param msg Content of the notification to show to the user +--@param log_level Optional log level +--@param opts Dictionary with optional options (timeout, etc) +function vim.notify(msg, log_level, _opts) + + if log_level == vim.log.levels.ERROR then + vim.api.nvim_err_writeln(msg) + else + vim.api.nvim_echo({{msg}}, true, {}) + end +end + + local on_keystroke_callbacks = {} --- Register a lua {fn} with an {id} to be run after every keystroke. @@ -534,4 +568,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 9f71df3a46..7064f2a068 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1404,9 +1404,9 @@ static void load_plugins(void) static void handle_quickfix(mparm_T *paramp) { if (paramp->edit_type == EDIT_QF) { - if (paramp->use_ef != NULL) - set_string_option_direct((char_u *)"ef", -1, - paramp->use_ef, OPT_FREE, SID_CARG); + if (paramp->use_ef != NULL) { + set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); + } vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ec4f4cbc21..73e3ba53a5 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -571,11 +571,12 @@ size_t mb_string2cells(const char_u *str) /// @param size maximum length of string. It will terminate on earlier NUL. /// @return The number of cells occupied by string `str` size_t mb_string2cells_len(const char_u *str, size_t size) + FUNC_ATTR_NONNULL_ARG(1) { size_t clen = 0; for (const char_u *p = str; *p != NUL && p < str+size; - p += utf_ptr2len_len(p, size+(p-str))) { + p += utfc_ptr2len_len(p, size+(p-str))) { clen += utf_ptr2cells(p); } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 31dc6b3649..34d8eb0ffe 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3859,8 +3859,8 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) /* May resize here so we don't have to do it in both cases below */ if (buf->b_ml.ml_usedchunks + 1 >= buf->b_ml.ml_numchunks) { buf->b_ml.ml_numchunks = buf->b_ml.ml_numchunks * 3 / 2; - buf->b_ml.ml_chunksize = (chunksize_T *) - xrealloc(buf->b_ml.ml_chunksize, + buf->b_ml.ml_chunksize = xrealloc( + buf->b_ml.ml_chunksize, sizeof(chunksize_T) * buf->b_ml.ml_numchunks); } @@ -4142,7 +4142,7 @@ void goto_byte(long cnt) if (lnum < 1) { // past the end curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; curwin->w_curswant = MAXCOL; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else { curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 9a6f29a908..dc4755f83d 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -45,16 +45,16 @@ typedef struct memline { memfile_T *ml_mfp; // pointer to associated memfile + infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs) + int ml_stack_top; // current top of ml_stack + int ml_stack_size; // total number of entries in ml_stack + #define ML_EMPTY 1 // empty buffer #define ML_LINE_DIRTY 2 // cached line was changed and allocated #define ML_LOCKED_DIRTY 4 // ml_locked was changed #define ML_LOCKED_POS 8 // ml_locked needs positive block number int ml_flags; - infoptr_T *ml_stack; // stack of pointer blocks (array of IPTRs) - int ml_stack_top; // current top of ml_stack - int ml_stack_size; // total number of entries in ml_stack - linenr_T ml_line_lnum; // line number of cached line, 0 if not valid char_u *ml_line_ptr; // pointer to cached line size_t ml_line_offset; // cached byte offset of ml_line_lnum diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 9bc6b23ce3..7a8fc4da75 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -19,6 +19,8 @@ #include "nvim/ui.h" #include "nvim/sign.h" #include "nvim/api/vim.h" +#include "nvim/lua/executor.h" +#include "nvim/decoration.h" #ifdef UNIT_TESTING # define malloc(size) mem_malloc(size) @@ -695,6 +697,10 @@ void free_all_mem(void) list_free_log(); check_quickfix_busy(); + + decor_free_all_mem(); + + nlua_free_all_mem(); } #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 7094d3be90..ac3b7768e6 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -81,7 +81,7 @@ ex_menu(exarg_T *eap) // kFalse for "menu disable vimmenu_T menuarg; - modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu); + modes = get_menu_cmd_modes((char *)eap->cmd, eap->forceit, &noremap, &unmenu); arg = eap->arg; for (;; ) { @@ -912,7 +912,9 @@ static int expand_emenu; /* TRUE for ":emenu" command */ /* * Work out what to complete when doing command line completion of menu names. */ -char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forceit) +char_u *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char_u *arg, + bool forceit) + FUNC_ATTR_NONNULL_ALL { char_u *after_dot; char_u *p; @@ -1178,7 +1180,7 @@ static bool menu_namecmp(const char_u *const name, const char_u *const mname) /// to whether the command is an "unmenu" command. int get_menu_cmd_modes( - const char_u * cmd, + const char *cmd, bool forceit, int *noremap, int *unmenu diff --git a/src/nvim/message.c b/src/nvim/message.c index ba7a667a60..7c98d3c6b5 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -178,6 +178,7 @@ void msg_grid_validate(void) msg_grid.throttled = false; // don't throttle in 'cmdheight' area msg_scrolled_at_flush = msg_scrolled; msg_grid.focusable = false; + msg_grid_adj.target = &msg_grid; if (!msg_scrolled) { msg_grid_set_pos(Rows - p_ch, false); } @@ -188,6 +189,7 @@ void msg_grid_validate(void) ui_call_grid_destroy(msg_grid.handle); msg_grid.throttled = false; msg_grid_adj.row_offset = 0; + msg_grid_adj.target = &default_grid; redraw_cmdline = true; } else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) { msg_grid_set_pos(Rows - p_ch, false); @@ -867,18 +869,18 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr) */ char_u *msg_may_trunc(int force, char_u *s) { - int n; int room; room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (n = (int)STRLEN(s) - room) > 0) { + && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); // There may be room anyway when there are multibyte chars. if (size <= room) { return s; } + int n; for (n = 0; size >= room; ) { size -= utf_ptr2cells(s + n); n += utfc_ptr2len(s + n); @@ -1703,6 +1705,7 @@ void msg_prt_line(char_u *s, int list) char_u *p_extra = NULL; // init to make SASC shut up int n; int attr = 0; + char_u *lead = NULL; char_u *trail = NULL; int l; @@ -1710,11 +1713,24 @@ void msg_prt_line(char_u *s, int list) list = true; } - // find start of trailing whitespace - if (list && curwin->w_p_lcs_chars.trail) { - trail = s + STRLEN(s); - while (trail > s && ascii_iswhite(trail[-1])) { - trail--; + if (list) { + // find start of trailing whitespace + if (curwin->w_p_lcs_chars.trail) { + trail = s + STRLEN(s); + while (trail > s && ascii_iswhite(trail[-1])) { + trail--; + } + } + // find end of leading whitespace + if (curwin->w_p_lcs_chars.lead) { + lead = s; + while (ascii_iswhite(lead[0])) { + lead++; + } + // in a line full of spaces all of them are treated as trailing + if (*lead == NUL) { + lead = NULL; + } } } @@ -1756,7 +1772,9 @@ void msg_prt_line(char_u *s, int list) c = *s++; if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) { // tab amount depends on current column - n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1; + n_extra = tabstop_padding(col, + curbuf->b_p_ts, + curbuf->b_p_vts_array) - 1; if (!list) { c = ' '; c_extra = ' '; @@ -1789,6 +1807,9 @@ void msg_prt_line(char_u *s, int list) /* Use special coloring to be able to distinguish <hex> from * the same in plain text. */ attr = HL_ATTR(HLF_8); + } else if (c == ' ' && lead != NULL && s <= lead) { + c = curwin->w_p_lcs_chars.lead; + attr = HL_ATTR(HLF_8); } else if (c == ' ' && trail != NULL && s > trail) { c = curwin->w_p_lcs_chars.trail; attr = HL_ATTR(HLF_8); diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index fcffe64595..34c43da0f7 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -412,7 +412,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) s = ml_get_buf(wp->w_buffer, lnum, FALSE); if (*s == NUL) /* empty line */ return 1; - col = win_linetabsize(wp, s, (colnr_T)MAXCOL); + col = win_linetabsize(wp, s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index ff471ea978..4c0339e5f4 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -72,9 +72,7 @@ int jump_to_mouse(int flags, int row = mouse_row; int col = mouse_col; int grid = mouse_grid; - int mouse_char; int fdc = 0; - ScreenGrid *gp = &default_grid; mouse_past_bottom = false; mouse_past_eol = false; @@ -303,25 +301,6 @@ retnomove: } } - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - if (ui_has(kUIMultigrid)) { - gp = &curwin->w_grid; - } - if (row >= 0 && row < Rows && col >= 0 && col <= Columns - && gp->chars != NULL) { - mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] - + (unsigned)col]); - } else { - mouse_char = ' '; - } - - // Check for position outside of the fold column. - if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : - col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { - mouse_char = ' '; - } - // compute the position in the buffer line from the posn on the screen if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { mouse_past_bottom = true; @@ -362,11 +341,7 @@ retnomove: count |= CURSOR_MOVED; // Cursor has moved } - if (mouse_char == curwin->w_p_fcs_chars.foldclosed) { - count |= MOUSE_FOLD_OPEN; - } else if (mouse_char != ' ') { - count |= MOUSE_FOLD_CLOSE; - } + count |= mouse_check_fold(); return count; } @@ -495,21 +470,21 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) *gridp = DEFAULT_GRID_HANDLE; } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); - if (wp && wp->w_grid.chars + if (wp && wp->w_grid_alloc.chars && !(wp->w_floating && !wp->w_float_config.focusable)) { - *rowp = MIN(*rowp, wp->w_grid.Rows-1); - *colp = MIN(*colp, wp->w_grid.Columns-1); + *rowp = MIN(*rowp-wp->w_grid.row_offset, wp->w_grid.Rows-1); + *colp = MIN(*colp-wp->w_grid.col_offset, wp->w_grid.Columns-1); return wp; } } else if (*gridp == 0) { ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (&wp->w_grid != grid) { + if (&wp->w_grid_alloc != grid) { continue; } *gridp = grid->handle; - *rowp -= grid->comp_row; - *colp -= grid->comp_col; + *rowp -= grid->comp_row+wp->w_grid.row_offset; + *colp -= grid->comp_col+wp->w_grid.col_offset; return wp; } @@ -738,3 +713,46 @@ static int mouse_adjust_click(win_T *wp, int row, int col) return col + nudge; } + +// Check clicked cell is foldcolumn +int mouse_check_fold(void) +{ + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; + int mouse_char = ' '; + + win_T *wp; + + wp = mouse_find_win(&click_grid, &click_row, &click_col); + + if (wp && mouse_row >= 0 && mouse_row < Rows + && mouse_col >= 0 && mouse_col <= Columns) { + int multigrid = ui_has(kUIMultigrid); + ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; + int fdc = win_fdccol_count(wp); + int row = multigrid && mouse_grid == 0 ? click_row : mouse_row; + int col = multigrid && mouse_grid == 0 ? click_col : mouse_col; + + // Remember the character under the mouse, might be one of foldclose or + // foldopen fillchars in the fold column. + if (gp->chars != NULL) { + mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] + + (unsigned)col]); + } + + // Check for position outside of the fold column. + if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : + click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { + mouse_char = ' '; + } + } + + if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { + return MOUSE_FOLD_OPEN; + } else if (mouse_char != ' ') { + return MOUSE_FOLD_CLOSE; + } + + return 0; +} diff --git a/src/nvim/move.c b/src/nvim/move.c index a6afdc27d9..1210a3365a 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -888,11 +888,11 @@ void curs_columns( } else { n = plines; } - if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width) { + if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) { extra += 2; } - if (extra == 3 || plines < so * 2) { + if (extra == 3 || plines <= so * 2) { // not enough room for 'scrolloff', put cursor in the middle n = wp->w_virtcol / width; if (n > wp->w_height_inner / 2) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a0b439ac45..a2d8859c68 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -219,7 +219,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, char buf[256]; snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); - call_set_error(channel, buf, WARN_LOG_LEVEL); + call_set_error(channel, buf, INFO_LOG_LEVEL); goto end; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8f22243348..c948881eca 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -630,9 +630,9 @@ static void normal_redraw_mode_message(NormalState *s) ui_cursor_shape(); // show different cursor shape ui_flush(); if (msg_scroll || emsg_on_display) { - os_delay(1000L, true); // wait at least one second + os_delay(1003L, true); // wait at least one second } - os_delay(3000L, false); // wait up to three seconds + os_delay(3003L, false); // wait up to three seconds State = save_State; msg_scroll = false; @@ -2404,8 +2404,8 @@ do_mouse ( start_visual.lnum = 0; - /* Check for clicking in the tab page line. */ - if (mouse_row == 0 && firstwin->w_winrow > 0) { + // Check for clicking in the tab page line. + if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { if (is_drag) { if (in_tab_line) { move_tab_to_mouse(); @@ -3971,7 +3971,8 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) while (dist--) { if (dir == BACKWARD) { - if (curwin->w_curswant >= width1) { + if (curwin->w_curswant >= width1 + && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { // Move back within the line. This can give a negative value // for w_curswant if width1 < width2 (with cpoptions+=n), // which will get clipped to column 0. @@ -4003,14 +4004,16 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1; else n = width1; - if (curwin->w_curswant + width2 < (colnr_T)n) - /* move forward within line */ + if (curwin->w_curswant + width2 < (colnr_T)n + && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + // move forward within line curwin->w_curswant += width2; - else { - /* to next line */ - /* Move to the end of a closed fold. */ + } else { + // to next line + + // Move to the end of a closed fold. (void)hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum); + &curwin->w_cursor.lnum); if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { retval = false; break; @@ -5225,16 +5228,13 @@ static void nv_left(cmdarg_T *cap) * 'h' wraps to previous line if 'whichwrap' has 'h'. * CURS_LEFT wraps to previous line if 'whichwrap' has '<'. */ - if ( (((cap->cmdchar == K_BS - || cap->cmdchar == Ctrl_H) - && vim_strchr(p_ww, 'b') != NULL) - || (cap->cmdchar == 'h' - && vim_strchr(p_ww, 'h') != NULL) - || (cap->cmdchar == K_LEFT - && vim_strchr(p_ww, '<') != NULL)) - && curwin->w_cursor.lnum > 1) { - --(curwin->w_cursor.lnum); - coladvance((colnr_T)MAXCOL); + if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H) + && vim_strchr(p_ww, 'b') != NULL) + || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL) + || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) + && curwin->w_cursor.lnum > 1) { + curwin->w_cursor.lnum--; + coladvance(MAXCOL); curwin->w_set_curswant = true; // When the NL before the first char has to be deleted we @@ -5462,7 +5462,7 @@ static int normal_search( curwin->w_set_curswant = true; memset(&sia, 0, sizeof(sia)); - i = do_search(cap->oap, dir, pat, cap->count1, + i = do_search(cap->oap, dir, dir, pat, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; @@ -5792,16 +5792,20 @@ static void nv_percent(cmdarg_T *cap) } else { cap->oap->motion_type = kMTLineWise; setpcmark(); - /* Round up, so CTRL-G will give same value. Watch out for a - * large line count, the line number must not go negative! */ - if (curbuf->b_ml.ml_line_count > 1000000) + // Round up, so 'normal 100%' always jumps at the line line. + // Beyond 21474836 lines, (ml_line_count * 100 + 99) would + // overflow on 32-bits, so use a formula with less accuracy + // to avoid overflows. + if (curbuf->b_ml.ml_line_count >= 21474836) { curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count + 99L) / 100L * cap->count0; - else + } else { curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count * cap->count0 + 99L) / 100L; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + } + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } beginline(BL_SOL | BL_FIX); } } else { // "%" : go to matching paren @@ -6467,7 +6471,7 @@ static void nv_visual(cmdarg_T *cap) } if (resel_VIsual_vcol == MAXCOL) { curwin->w_curswant = MAXCOL; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } else if (VIsual_mode == Ctrl_V) { validate_virtcol(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); @@ -6776,9 +6780,10 @@ static void nv_g_cmd(cmdarg_T *cap) } coladvance((colnr_T)i); if (flag) { - do + do { i = gchar_cursor(); - while (ascii_iswhite(i) && oneright()); + } while (ascii_iswhite(i) && oneright()); + curwin->w_valid &= ~VALID_WCOL; } curwin->w_set_curswant = true; break; @@ -7570,6 +7575,12 @@ static void nv_esc(cmdarg_T *cap) got_int = false; /* don't stop executing autocommands et al. */ return; } + } else if (cmdwin_type != 0 && ex_normal_busy) { + // When :normal runs out of characters while in the command line window + // vgetorpeek() will return ESC. Exit the cmdline window to break the + // loop. + cmdwin_result = K_IGNORE; + return; } if (VIsual_active) { @@ -7600,7 +7611,7 @@ void set_cursor_for_append_to_line(void) // Pretend Insert mode here to allow the cursor on the // character past the end of the line State = INSERT; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); State = save_State; } else { curwin->w_cursor.col += (colnr_T)STRLEN(get_cursor_pos_ptr()); @@ -7988,7 +7999,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) * line. */ if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); + coladvance(MAXCOL); } } auto_format(false, true); @@ -8096,7 +8107,7 @@ static void nv_event(cmdarg_T *cap) // lists or dicts being used. may_garbage_collect = false; bool may_restart = (restart_edit != 0); - multiqueue_process_events(main_loop.events); + state_handle_k_event(); finish_op = false; if (may_restart) { // Tricky: if restart_edit was set before the handler we are in ctrl-o mode, diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 052b07ed44..190ca2e93b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -288,7 +288,7 @@ void shift_line( { int count; int i, j; - int p_sw = get_sw_value(curbuf); + int p_sw = (int)get_sw_value_indent(curbuf); count = get_indent(); // get current indent @@ -332,8 +332,9 @@ static void shift_block(oparg_T *oap, int amount) const int oldstate = State; char_u *newp; const int oldcol = curwin->w_cursor.col; - const int p_sw = get_sw_value(curbuf); - const int p_ts = (int)curbuf->b_p_ts; + int p_sw = (int)get_sw_value_indent(curbuf); + long *p_vts = curbuf->b_p_vts_array; + const long p_ts = curbuf->b_p_ts; struct block_def bd; int incr; int i = 0, j = 0; @@ -383,12 +384,11 @@ static void shift_block(oparg_T *oap, int amount) } /* OK, now total=all the VWS reqd, and textstart points at the 1st * non-ws char in the block. */ - if (!curbuf->b_p_et) - i = ((ws_vcol % p_ts) + total) / p_ts; /* number of tabs */ - if (i) - j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */ - else + if (!curbuf->b_p_et) { + tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j); + } else { j = total; + } // if we're splitting a TAB, allow for it int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0); @@ -1079,13 +1079,15 @@ do_execreg( } } escaped = vim_strsave_escape_csi(reg->y_array[i]); - retval = ins_typebuf(escaped, remap, 0, TRUE, silent); + retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); - if (retval == FAIL) + if (retval == FAIL) { return FAIL; - if (colon && ins_typebuf((char_u *)":", remap, 0, TRUE, silent) - == FAIL) + } + if (colon + && ins_typebuf((char_u *)":", remap, 0, true, silent) == FAIL) { return FAIL; + } } reg_executing = regname == 0 ? '"' : regname; // disable the 'q' command } @@ -1109,8 +1111,9 @@ static void put_reedit_in_typebuf(int silent) buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); buf[1] = NUL; } - if (ins_typebuf(buf, REMAP_NONE, 0, TRUE, silent) == OK) + if (ins_typebuf(buf, REMAP_NONE, 0, true, silent) == OK) { restart_edit = NUL; + } } } @@ -1130,25 +1133,29 @@ static int put_in_typebuf( int retval = OK; put_reedit_in_typebuf(silent); - if (colon) - retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, TRUE, silent); + if (colon) { + retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, true, silent); + } if (retval == OK) { char_u *p; - if (esc) + if (esc) { p = vim_strsave_escape_csi(s); - else + } else { p = s; - if (p == NULL) + } + if (p == NULL) { retval = FAIL; - else - retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, - 0, TRUE, silent); - if (esc) + } else { + retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, 0, true, silent); + } + if (esc) { xfree(p); + } + } + if (colon && retval == OK) { + retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, true, silent); } - if (colon && retval == OK) - retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, TRUE, silent); return retval; } @@ -1669,12 +1676,18 @@ int op_delete(oparg_T *oap) curbuf_splice_pending++; pos_T startpos = curwin->w_cursor; // start position for delete + bcount_t deleted_bytes = (bcount_t)STRLEN( + ml_get(startpos.lnum)) + 1 - startpos.col; truncate_line(true); // delete from cursor to end of line curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; + + for (linenr_T i = 1; i <= oap->line_count - 2; i++) { + deleted_bytes += (bcount_t)STRLEN( + ml_get(startpos.lnum + i)) + 1; + } del_lines(oap->line_count - 2, false); - bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col; // delete from start of line until op_end n = (oap->end.col + 1 - !oap->inclusive); @@ -2623,7 +2636,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } // NOTREACHED case kMTUnknown: - assert(false); + abort(); } } @@ -2800,7 +2813,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) size_t y_size; size_t oldlen; int y_width = 0; - colnr_T vcol; + colnr_T vcol = 0; int delcount; int incr = 0; struct block_def bd; @@ -3061,14 +3074,17 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (gchar_cursor() == TAB) { /* Don't need to insert spaces when "p" on the last position of a * tab or "P" on the first position. */ + int viscol = getviscol(); if (dir == FORWARD - ? (int)curwin->w_cursor.coladd < curbuf->b_p_ts - 1 - : curwin->w_cursor.coladd > 0) - coladvance_force(getviscol()); - else + ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1 + : curwin->w_cursor.coladd > 0) { + coladvance_force(viscol); + } else { curwin->w_cursor.coladd = 0; - } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) + } + } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) { coladvance_force(getviscol() + (dir == FORWARD)); + } } lnum = curwin->w_cursor.lnum; @@ -3177,7 +3193,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // insert the new text totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); - newp = (char_u *) xmalloc(totlen + oldlen + 1); + int addcount = (int)totlen + lines_appended; + newp = (char_u *)xmalloc(totlen + oldlen + 1); // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); @@ -3194,6 +3211,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if ((j < count - 1 || !shortline) && spaces) { memset(ptr, ' ', (size_t)spaces); ptr += spaces; + } else { + addcount -= spaces; } } // may insert some spaces after the new text @@ -3205,7 +3224,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, - delcount, (int)totlen + lines_appended, kExtmarkUndo); + delcount, addcount, kExtmarkUndo); ++curwin->w_cursor.lnum; if (i == 0) @@ -3422,15 +3441,11 @@ error: if (dir == FORWARD) curbuf->b_op_start.lnum++; } - // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. - if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count) { - ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) - ? kExtmarkUndo : kExtmarkNOOP; - mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), - (linenr_T)MAXLNUM, nr_lines, 0L, kind); - } + + ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) + ? kExtmarkUndo : kExtmarkNOOP; + mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), + (linenr_T)MAXLNUM, nr_lines, 0L, kind); // note changed text for displaying and folding if (y_type == kMTCharWise) { @@ -4309,11 +4324,12 @@ format_lines( * tabs and spaces, according to current options */ (void)set_indent(get_indent(), SIN_CHANGED); - /* put cursor on last non-space */ - State = NORMAL; /* don't go past end-of-line */ - coladvance((colnr_T)MAXCOL); - while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) + // put cursor on last non-space + State = NORMAL; // don't go past end-of-line + coladvance(MAXCOL); + while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { dec_cursor(); + } /* do the formatting, without 'showmode' */ State = INSERT; /* for open_line() */ @@ -6092,7 +6108,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 74bf6f0590..666c526a18 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -180,6 +180,8 @@ static long p_ts; static long p_tw; static int p_udf; static long p_wm; +static char_u *p_vsts; +static char_u *p_vts; static char_u *p_keymap; // Saved values for when 'bin' is set. @@ -194,6 +196,7 @@ static int p_et_nopaste; static long p_sts_nopaste; static long p_tw_nopaste; static long p_wm_nopaste; +static char_u *p_vsts_nopaste; typedef struct vimoption { char *fullname; // full option name @@ -379,8 +382,8 @@ void set_init_1(bool clean_arg) # else static char *(names[3]) = {"TMPDIR", "TEMP", "TMP"}; # endif - int len; garray_T ga; + opt_idx = findoption("backupskip"); ga_init(&ga, 1, 100); for (size_t n = 0; n < ARRAY_SIZE(names); n++) { @@ -401,15 +404,23 @@ void set_init_1(bool clean_arg) } if (p != NULL && *p != NUL) { // First time count the NUL, otherwise count the ','. - len = (int)strlen(p) + 3; - ga_grow(&ga, len); - if (!GA_EMPTY(&ga)) { - STRCAT(ga.ga_data, ","); + const size_t len = strlen(p) + 3; + char *item = xmalloc(len); + xstrlcpy(item, p, len); + add_pathsep(item); + xstrlcat(item, "*", len); + if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags) + == NULL) { + ga_grow(&ga, (int)len); + if (!GA_EMPTY(&ga)) { + STRCAT(ga.ga_data, ","); + } + STRCAT(ga.ga_data, p); + add_pathsep(ga.ga_data); + STRCAT(ga.ga_data, "*"); + ga.ga_len += (int)len; } - STRCAT(ga.ga_data, p); - add_pathsep(ga.ga_data); - STRCAT(ga.ga_data, "*"); - ga.ga_len += len; + xfree(item); } if(mustfree) { xfree(p); @@ -713,6 +724,38 @@ static void set_string_default(const char *name, char *val, bool allocated) } } +// For an option value that contains comma separated items, find "newval" in +// "origval". Return NULL if not found. +static char_u *find_dup_item(char_u *origval, const char_u *newval, + uint32_t flags) + FUNC_ATTR_NONNULL_ARG(2) +{ + int bs = 0; + + if (origval == NULL) { + return NULL; + } + + const size_t newlen = STRLEN(newval); + for (char_u *s = origval; *s != NUL; s++) { + if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) + && STRNCMP(s, newval, newlen) == 0 + && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) { + return s; + } + // Count backslashes. Only a comma with an even number of backslashes + // or a single backslash preceded by a comma before it is recognized as + // a separator. + if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') + || (s == origval + 1 && s[-1] == '\\')) { + bs++; + } else { + bs = 0; + } + } + return NULL; +} + /// Set the Vi-default value of a number option. /// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) @@ -1285,9 +1328,7 @@ int do_set( char *saved_newval = NULL; unsigned newlen; int comma; - int bs; - int new_value_alloced; /* new string option - was allocated */ + bool new_value_alloced = false; // new string option was allocated /* When using ":set opt=val" for a global option * with a local value the local value will be @@ -1486,34 +1527,20 @@ int do_set( i = 0; // init for GCC if (removing || (flags & P_NODUP)) { i = (int)STRLEN(newval); - bs = 0; - for (s = origval; *s; s++) { - if ((!(flags & P_COMMA) - || s == origval - || (s[-1] == ',' && !(bs & 1))) - && STRNCMP(s, newval, i) == 0 - && (!(flags & P_COMMA) - || s[i] == ',' - || s[i] == NUL)) { - break; - } - // Count backslashes. Only a comma with an even number of - // backslashes or a single backslash preceded by a comma - // before it is recognized as a separator - if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') - || (s == origval + 1 && s[-1] == '\\')) { - bs++; - } else { - bs = 0; - } - } + s = find_dup_item(origval, newval, flags); // do not add if already there - if ((adding || prepending) && *s) { + if ((adding || prepending) && s != NULL) { prepending = false; adding = false; STRCPY(newval, origval); } + + // if no duplicate, move pointer to end of + // original value + if (s == NULL) { + s = origval + (int)STRLEN(origval); + } } /* concatenate the two strings; add a ',' if @@ -1942,6 +1969,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); @@ -1973,6 +2001,10 @@ static void didset_options2(void) // Parse default for 'wildmode'. check_opt_wim(); + xfree(curbuf->b_p_vsts_array); + tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); + xfree(curbuf->b_p_vts_array); + tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); } /// Check for string options that are NULL (normally only termcap options). @@ -2039,6 +2071,8 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); check_string_option(&buf->b_p_menc); + check_string_option(&buf->b_p_vsts); + check_string_option(&buf->b_p_vts); } /// Free the string allocated for an option. @@ -2085,9 +2119,12 @@ int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags) /// Get a pointer to the flags used for the P_INSECURE flag of option /// "opt_idx". For some local options a local flags field is used. +/// NOTE: Caller must make sure that "wp" is set to the window from which +/// the option is used. static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { - if (opt_flags & OPT_LOCAL) + if (opt_flags & OPT_LOCAL) { + assert(wp != NULL); switch ((int)options[opt_idx].indir) { case PV_STL: return &wp->w_p_stl_flags; case PV_FDE: return &wp->w_p_fde_flags; @@ -2096,6 +2133,7 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) case PV_FEX: return &wp->w_buffer->b_p_fex_flags; case PV_INEX: return &wp->w_buffer->b_p_inex_flags; } + } // Nothing special, return global flags field. return &options[opt_idx].flags; @@ -2119,9 +2157,9 @@ static int shada_idx = -1; // "set_sid". void set_string_option_direct( - char_u *name, + const char *name, int opt_idx, - char_u *val, + const char_u *val, int opt_flags, // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL int set_sid ) @@ -2132,7 +2170,7 @@ set_string_option_direct( int idx = opt_idx; if (idx == -1) { // Use name. - idx = findoption((const char *)name); + idx = findoption(name); if (idx < 0) { // Not found (should not happen). internal_error("set_string_option_direct()"); IEMSG2(_("For option %s"), name); @@ -2305,7 +2343,7 @@ static char_u * did_set_string_option( int opt_idx, // index in options[] table char_u **varp, // pointer to the option variable - int new_value_alloced, // new value was allocated + bool new_value_alloced, // new value was allocated char_u *oldval, // previous value of the option char_u *errbuf, // buffer for errors, or NULL size_t errbuflen, // length of errors buffer @@ -3077,6 +3115,69 @@ 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 if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' + char_u *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + if (curbuf->b_p_vsts_array) { + xfree(curbuf->b_p_vsts_array); + curbuf->b_p_vsts_array = 0; + } + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vsts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { + xfree(oldarray); + } else { + errmsg = e_invarg; + } + } + } + } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' + char_u *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + if (curbuf->b_p_vts_array) { + xfree(curbuf->b_p_vts_array); + curbuf->b_p_vts_array = NULL; + } + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { + xfree(oldarray); + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else { + errmsg = e_invarg; + } + } + } } else { // Options that are a list of flags. p = NULL; @@ -3336,6 +3437,12 @@ skip: return NULL; // no error } +void check_blending(win_T *wp) +{ + wp->w_grid_alloc.blending = + wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow); +} + /// Handle setting 'listchars' or 'fillchars'. /// Assume monocell characters @@ -3376,6 +3483,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_lcs_chars.prec, "precedes", NUL }, { &wp->w_p_lcs_chars.space, "space", NUL }, { &wp->w_p_lcs_chars.tab2, "tab", NUL }, + { &wp->w_p_lcs_chars.lead, "lead", NUL }, { &wp->w_p_lcs_chars.trail, "trail", NUL }, { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; @@ -3786,7 +3894,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, if (p_terse && p == NULL) { STRCPY(IObuff, p_shm); STRCAT(IObuff, "s"); - set_string_option_direct((char_u *)"shm", -1, IObuff, OPT_FREE, 0); + set_string_option_direct("shm", -1, IObuff, OPT_FREE, 0); } else if (!p_terse && p != NULL) { // remove 's' from p_shm STRMOVE(p, p + 1); } @@ -4278,7 +4386,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // 'floatblend' curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0); curwin->w_hl_needs_update = true; - curwin->w_grid.blending = curwin->w_p_winbl > 0; + check_blending(curwin); } @@ -4526,7 +4634,7 @@ bool is_tty_option(const char *name) #define TCO_BUFFER_SIZE 8 /// @param name TUI-related option /// @param[out,allocated] value option string value -bool get_tty_option(char *name, char **value) +bool get_tty_option(const char *name, char **value) { if (strequal(name, "t_Co")) { if (value) { @@ -4592,6 +4700,7 @@ bool set_tty_option(const char *name, char *value) /// /// @return Option index or -1 if option was not found. static int findoption(const char *const arg) + FUNC_ATTR_NONNULL_ALL { return findoption_len(arg, strlen(arg)); } @@ -4605,17 +4714,17 @@ static int findoption(const char *const arg) /// hidden String option: -2. /// unknown option: -3. int get_option_value( - char_u *name, + const char *name, long *numval, char_u **stringval, ///< NULL when only checking existence int opt_flags ) { - if (get_tty_option((char *)name, (char **)stringval)) { + if (get_tty_option(name, (char **)stringval)) { return 0; } - int opt_idx = findoption((const char *)name); + int opt_idx = findoption(name); if (opt_idx < 0) { // Unknown option. return -3; } @@ -5650,6 +5759,8 @@ static char_u *get_varp(vimoption_T *p) case PV_TW: return (char_u *)&(curbuf->b_p_tw); case PV_UDF: return (char_u *)&(curbuf->b_p_udf); case PV_WM: return (char_u *)&(curbuf->b_p_wm); + case PV_VSTS: return (char_u *)&(curbuf->b_p_vsts); + case PV_VTS: return (char_u *)&(curbuf->b_p_vts); case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); @@ -5790,7 +5901,8 @@ void didset_window_options(win_T *wp) set_chars_option(wp, &wp->w_p_fcs, true); set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl - wp->w_grid.blending = wp->w_p_winbl > 0; + check_blending(wp); + wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -5901,6 +6013,15 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_tfu = vim_strsave(p_tfu); buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; + buf->b_p_vsts = vim_strsave(p_vsts); + if (p_vsts && p_vsts != empty_option) { + tabstop_set(p_vsts, &buf->b_p_vsts_array); + } else { + buf->b_p_vsts_array = 0; + } + buf->b_p_vsts_nopaste = p_vsts_nopaste + ? vim_strsave(p_vsts_nopaste) + : NULL; buf->b_p_com = vim_strsave(p_com); buf->b_p_cms = vim_strsave(p_cms); buf->b_p_fo = vim_strsave(p_fo); @@ -5972,10 +6093,21 @@ void buf_copy_options(buf_T *buf, int flags) */ if (dont_do_help) { buf->b_p_isk = save_p_isk; + if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + tabstop_set(p_vts, &buf->b_p_vts_array); + } else { + buf->b_p_vts_array = NULL; + } } else { buf->b_p_isk = vim_strsave(p_isk); did_isk = true; buf->b_p_ts = p_ts; + buf->b_p_vts = vim_strsave(p_vts); + if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + tabstop_set(p_vts, &buf->b_p_vts_array); + } else { + buf->b_p_vts_array = NULL; + } buf->b_help = false; if (buf->b_p_bt[0] == 'h') { clear_string_option(&buf->b_p_bt); @@ -6172,6 +6304,8 @@ set_context_in_set_cmd( xp->xp_backslash = XP_BS_THREE; else xp->xp_backslash = XP_BS_ONE; + } else if (p == (char_u *)&p_ft) { + xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space @@ -6588,6 +6722,12 @@ static void paste_option_changed(void) buf->b_p_sts_nopaste = buf->b_p_sts; buf->b_p_ai_nopaste = buf->b_p_ai; buf->b_p_et_nopaste = buf->b_p_et; + if (buf->b_p_vsts_nopaste) { + xfree(buf->b_p_vsts_nopaste); + } + buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option + ? vim_strsave(buf->b_p_vsts) + : NULL; } // save global options @@ -6602,6 +6742,12 @@ static void paste_option_changed(void) p_sts_nopaste = p_sts; p_tw_nopaste = p_tw; p_wm_nopaste = p_wm; + if (p_vsts_nopaste) { + xfree(p_vsts_nopaste); + } + p_vsts_nopaste = p_vsts && p_vsts != empty_option + ? vim_strsave(p_vsts) + : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -6613,6 +6759,14 @@ static void paste_option_changed(void) buf->b_p_sts = 0; // softtabstop is 0 buf->b_p_ai = 0; // no auto-indent buf->b_p_et = 0; // no expandtab + if (buf->b_p_vsts) { + free_string_option(buf->b_p_vsts); + } + buf->b_p_vsts = empty_option; + if (buf->b_p_vsts_array) { + xfree(buf->b_p_vsts_array); + } + buf->b_p_vsts_array = 0; } // set global options @@ -6629,6 +6783,10 @@ static void paste_option_changed(void) p_wm = 0; p_sts = 0; p_ai = 0; + if (p_vsts) { + free_string_option(p_vsts); + } + p_vsts = empty_option; } else if (old_p_paste) { // Paste switched from on to off: Restore saved values. @@ -6639,6 +6797,20 @@ static void paste_option_changed(void) buf->b_p_sts = buf->b_p_sts_nopaste; buf->b_p_ai = buf->b_p_ai_nopaste; buf->b_p_et = buf->b_p_et_nopaste; + if (buf->b_p_vsts) { + free_string_option(buf->b_p_vsts); + } + buf->b_p_vsts = buf->b_p_vsts_nopaste + ? vim_strsave(buf->b_p_vsts_nopaste) + : empty_option; + if (buf->b_p_vsts_array) { + xfree(buf->b_p_vsts_array); + } + if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { + tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); + } else { + buf->b_p_vsts_array = 0; + } } // restore global options @@ -6656,6 +6828,10 @@ static void paste_option_changed(void) p_sts = p_sts_nopaste; p_tw = p_tw_nopaste; p_wm = p_wm_nopaste; + if (p_vsts) { + free_string_option(p_vsts); + } + p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option; } old_p_paste = p_paste; @@ -6905,17 +7081,301 @@ int check_ff_value(char_u *p) return check_opt_strings(p, p_ff_values, false); } +// Set the integer values corresponding to the string setting of 'vartabstop'. +// "array" will be set, caller must free it if needed. +bool tabstop_set(char_u *var, long **array) +{ + long valcount = 1; + int t; + char_u *cp; + + if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { + *array = NULL; + return true; + } + + for (cp = var; *cp != NUL; cp++) { + if (cp == var || cp[-1] == ',') { + char_u *end; + + if (strtol((char *)cp, (char **)&end, 10) <= 0) { + if (cp != end) { + EMSG(_(e_positive)); + } else { + EMSG(_(e_invarg)); + } + return false; + } + } + + if (ascii_isdigit(*cp)) { + continue; + } + if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { + valcount++; + continue; + } + EMSG(_(e_invarg)); + return false; + } + + *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); + (*array)[0] = valcount; + + t = 1; + for (cp = var; *cp != NUL;) { + (*array)[t++] = atoi((char *)cp); + while (*cp != NUL && *cp != ',') { + cp++; + } + if (*cp != NUL) { + cp++; + } + } + + return true; +} + +// Calculate the number of screen spaces a tab will occupy. +// If "vts" is set then the tab widths are taken from that array, +// otherwise the value of ts is used. +int tabstop_padding(colnr_T col, long ts_arg, long *vts) +{ + long ts = ts_arg == 0 ? 8 : ts_arg; + colnr_T tabcol = 0; + int t; + long padding = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)(ts - (col % ts)); + } + + const long tabcount = vts[0]; + + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + padding = tabcol - col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); + } + + return (int)padding; +} + +// Find the size of the tab that covers a particular column. +int tabstop_at(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + long tab_size = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)ts; + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + tab_size = vts[t]; + break; + } + } + if (t > tabcount) { + tab_size = vts[tabcount]; + } + + return (int)tab_size; +} + +// Find the column on which a tab starts. +colnr_T tabstop_start(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + + if (vts == NULL || vts[0] == 0) { + return (int)((col / ts) * ts); + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + return (int)(tabcol - vts[t]); + } + } + + const int excess = (int)(tabcol % vts[tabcount]); + return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); +} + +// Find the number of tabs and spaces necessary to get from one column +// to another. +void tabstop_fromto(colnr_T start_col, + colnr_T end_col, + long ts_arg, + long *vts, + int *ntabs, + int *nspcs) +{ + int spaces = end_col - start_col; + colnr_T tabcol = 0; + long padding = 0; + int t; + long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + + if (vts == NULL || vts[0] == 0) { + int tabs = 0; + + const int initspc = (int)(ts - (start_col % ts)); + if (spaces >= initspc) { + spaces -= initspc; + tabs++; + } + tabs += (int)(spaces / ts); + spaces -= (int)((spaces / ts) * ts); + + *ntabs = tabs; + *nspcs = spaces; + return; + } + + // Find the padding needed to reach the next tabstop. + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > start_col) { + padding = tabcol - start_col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); + } + + // If the space needed is less than the padding no tabs can be used. + if (spaces < padding) { + *ntabs = 0; + *nspcs = spaces; + return; + } + + *ntabs = 1; + spaces -= (int)padding; + + // At least one tab has been used. See if any more will fit. + while (spaces != 0 && ++t <= tabcount) { + padding = vts[t]; + if (spaces < padding) { + *nspcs = spaces; + return; + } + *ntabs += 1; + spaces -= (int)padding; + } + + *ntabs += spaces / (int)vts[tabcount]; + *nspcs = spaces % (int)vts[tabcount]; +} + +// See if two tabstop arrays contain the same values. +bool tabstop_eq(long *ts1, long *ts2) +{ + int t; + + if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { + return false; + } + if (ts1 == ts2) { + return true; + } + if (ts1[0] != ts2[0]) { + return false; + } + + for (t = 1; t <= ts1[0]; t++) { + if (ts1[t] != ts2[t]) { + return false; + } + } + + return true; +} + +// Copy a tabstop array, allocating space for the new array. +int *tabstop_copy(long *oldts) +{ + long *newts; + int t; + + if (oldts == 0) { + return 0; + } + + newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); + for (t = 0; t <= oldts[0]; t++) { + newts[t] = oldts[t]; + } + + return (int *)newts; +} + +// Return a count of the number of tabstops. +int tabstop_count(long *ts) +{ + return ts != NULL ? (int)ts[0] : 0; +} + +// Return the first tabstop, or 8 if there are no tabstops defined. +int tabstop_first(long *ts) +{ + return ts != NULL ? (int)ts[1] : 8; +} + /// Return the effective shiftwidth value for current buffer, using the /// 'tabstop' value when 'shiftwidth' is zero. int get_sw_value(buf_T *buf) { - long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts; + long result = get_sw_value_col(buf, 0); assert(result >= 0 && result <= INT_MAX); return (int)result; } +// Idem, using the first non-black in the current line. +long get_sw_value_indent(buf_T *buf) +{ + pos_T pos = curwin->w_cursor; + + pos.col = (colnr_T)getwhitecols_curline(); + return get_sw_value_pos(buf, &pos); +} + +// Idem, using "pos". +long get_sw_value_pos(buf_T *buf, pos_T *pos) +{ + pos_T save_cursor = curwin->w_cursor; + long sw_value; + + curwin->w_cursor = *pos; + sw_value = get_sw_value_col(buf, get_nolist_virtcol()); + curwin->w_cursor = save_cursor; + return sw_value; +} + +// Idem, using virtual column "col". +long get_sw_value_col(buf_T *buf, colnr_T col) +{ + return buf->b_p_sw ? buf->b_p_sw + : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); +} + /// Return the effective softtabstop value for the current buffer, -/// using the effective shiftwidth value when 'softtabstop' is negative. +/// using the shiftwidth value when 'softtabstop' is negative. int get_sts_value(void) { long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; @@ -7049,7 +7509,7 @@ void set_fileformat(int eol_style, int opt_flags) // p is NULL if "eol_style" is EOL_UNKNOWN. if (p != NULL) { - set_string_option_direct((char_u *)"ff", + set_string_option_direct("ff", -1, (char_u *)p, OPT_FREE | opt_flags, @@ -7123,9 +7583,19 @@ int csh_like_shell(void) /// buffer signs and on user configuration. int win_signcol_count(win_T *wp) { + return win_signcol_configured(wp, NULL); +} + +/// Return the number of requested sign columns, based on user / configuration. +int win_signcol_configured(win_T *wp, int *is_fixed) +{ int minimum = 0, maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; + if (is_fixed) { + *is_fixed = 1; + } + // Note: It checks "no" or "number" in 'signcolumn' option if (*scl == 'n' && (*(scl + 1) == 'o' || (*(scl + 1) == 'u' @@ -7143,7 +7613,11 @@ int win_signcol_count(win_T *wp) return 1; } - // auto or auto:<NUM> + if (is_fixed) { + // auto or auto:<NUM> + *is_fixed = 0; + } + if (!strncmp(scl, "auto:", 5)) { // Variable depending on a configuration maximum = scl[5] - '0'; @@ -7154,7 +7628,9 @@ int win_signcol_count(win_T *wp) } } - return MAX(minimum, MIN(maximum, needed_signcols)); + int ret = MAX(minimum, MIN(maximum, needed_signcols)); + assert(ret <= SIGN_SHOW_MAX); + return ret; } /// Get window or buffer local options diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 683afc670e..16749ba86b 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -165,8 +165,8 @@ enum { SHM_WRI = 'w', ///< "[w]" instead of "written". SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS. SHM_WRITE = 'W', ///< Don't use "written" at all. - SHM_TRUNC = 't', ///< Trunctate file messages. - SHM_TRUNCALL = 'T', ///< Trunctate all messages. + SHM_TRUNC = 't', ///< Truncate file messages. + SHM_TRUNCALL = 'T', ///< Truncate all messages. SHM_OVER = 'o', ///< Overwrite file messages. SHM_OVERALL = 'O', ///< Overwrite more messages. SHM_SEARCH = 's', ///< No search hit bottom messages. @@ -621,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' @@ -811,6 +824,8 @@ enum { , BV_UDF , BV_UL , BV_WM + , BV_VSTS + , BV_VTS , BV_COUNT // must be the last one }; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index df2bfbce34..d12b31bcaf 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -900,6 +900,7 @@ return { normal_fname_chars=true, vi_def=true, alloced=true, + expand=true, varname='p_ft', defaults={if_true={vi=""}} }, @@ -2821,6 +2822,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'}, @@ -2990,6 +2999,23 @@ return { defaults={if_true={vi=4000}} }, { + full_name='varsofttabstop', abbreviation='vsts', + short_desc=N_("list of numbers of spaces that <Tab> uses while editing"), + type='string', list='comma', scope={'buffer'}, + vi_def=true, + varname='p_vsts', + defaults={if_true={vi=""}} + }, + { + full_name='vartabstop', abbreviation='vts', + short_desc=N_("list of numbers of spaces that <Tab> in file uses"), + type='string', list='comma', scope={'buffer'}, + vi_def=true, + varname='p_vts', + redraw={'current_buffer'}, + defaults={if_true={vi=""}} + }, + { full_name='verbose', abbreviation='vbs', short_desc=N_("give informative messages"), type='number', scope={'global'}, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 9d6518841a..eca245650a 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -159,16 +159,28 @@ bool os_char_avail(void) return inbuf_poll(0, NULL) == kInputAvail; } -// Check for CTRL-C typed by reading all available characters. +/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed. +/// +/// This invokes a full libuv loop iteration which can be quite costly. +/// Prefer `line_breakcheck()` if called in a busy inner loop. +/// +/// Caller must at least check `got_int` before calling this function again. +/// checking for other low-level input state like `input_available()` might +/// also be relevant (i e to throttle idle processing when user input is +/// available) void os_breakcheck(void) { + if (got_int) { + return; + } + int save_us = updating_screen; // We do not want screen_resize() to redraw here. + // TODO(bfredl): we are already special casing redraw events, is this + // hack still needed? updating_screen++; - if (!got_int) { - loop_poll_events(&main_loop, 0); - } + loop_poll_events(&main_loop, 0); updating_screen = save_us; } diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 4d7d9a45df..36d6dbe2db 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); @@ -185,14 +175,17 @@ static void init_child(PtyProcess *ptyproc) Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - ELOG("chdir failed: %s", strerror(errno)); + ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno)); return; } char *prog = ptyproc->process.argv[0]; - os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); - execvp(prog, ptyproc->process.argv); - ELOG("execvp failed: %s: %s", strerror(errno), prog); + + assert(proc->env); + environ = tv_dict_to_env(proc->env); + execvp(prog, proc->argv); + ELOG("execvp(%s) failed: %s", prog, strerror(errno)); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h index f7c57b3839..8c822eafad 100644 --- a/src/nvim/os/pty_process_unix.h +++ b/src/nvim/os/pty_process_unix.h @@ -17,7 +17,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; rv.process = process_init(loop, kProcessTypePty, data); - rv.term_name = NULL; rv.width = 80; rv.height = 24; rv.tty_fd = -1; diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 6f7100e846..2bf73d08e6 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"; @@ -193,11 +203,13 @@ int pty_process_spawn(PtyProcess *ptyproc) cleanup: if (status) { // In the case of an error of MultiByteToWideChar or CreateProcessW. - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = os_translate_sys_error(status); } else if (err != NULL) { status = (int)winpty_error_code(err); - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = translate_winpty_error(status); } winpty_error_free(err); @@ -213,6 +225,7 @@ cleanup: xfree(in_req); xfree(out_req); xfree(cmd_line); + xfree(env); xfree(cwd); return status; } @@ -332,19 +345,17 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) utf8_cmd_line_len += argc; char *utf8_cmd_line = xmalloc(utf8_cmd_line_len); *utf8_cmd_line = NUL; - while (1) { - QUEUE *head = QUEUE_HEAD(&args_q); - QUEUE_REMOVE(head); - ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node); + QUEUE *q; + QUEUE_FOREACH(q, &args_q, { + ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); xfree(arg_node->arg); xfree(arg_node); - if (QUEUE_EMPTY(&args_q)) { - break; - } else { + QUEUE_REMOVE(q); + if (!QUEUE_EMPTY(&args_q)) { xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); } - } + }) int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line); xfree(utf8_cmd_line); @@ -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/pty_process_win.h b/src/nvim/os/pty_process_win.h index 8ad5ba7286..f8ec79a3d6 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -37,7 +37,6 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; rv.process = process_init(loop, kProcessTypePty, data); - rv.term_name = NULL; rv.width = 80; rv.height = 24; rv.object.winpty = NULL; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index b5d890bf52..2974245857 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -123,7 +123,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, int shell_style = STYLE_ECHO; int check_spaces; static bool did_find_nul = false; - bool ampersent = false; + bool ampersand = false; // vimglob() function to define for Posix shell static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; @@ -245,7 +245,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, p--; } if (*p == '&') { // remove trailing '&' - ampersent = true; + ampersand = true; *p = ' '; } STRCAT(command, ">"); @@ -309,7 +309,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, shellopts |= kShellOptHideMess; } - if (ampersent) { + if (ampersand) { STRCAT(command, "&"); // put the '&' after the redirection } @@ -331,7 +331,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, // When running in the background, give it some time to create the temp // file, but don't wait for it to finish. - if (ampersent) { + if (ampersand) { os_delay(10L, true); } diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 4b6533cd0c..9ea74716aa 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -62,6 +62,7 @@ uint64_t os_now(void) /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. void os_delay(uint64_t ms, bool ignoreinput) { + DLOG("%" PRIu64 " ms", ms); if (ignoreinput) { if (ms > INT_MAX) { ms = INT_MAX; @@ -97,7 +98,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. @@ -196,6 +197,22 @@ char *os_ctime(char *result, size_t result_len) return os_ctime_r(&rawtime, result, result_len); } +/// Portable version of POSIX strptime() +/// +/// @param str[in] string to convert +/// @param format[in] format to parse "str" +/// @param tm[out] time representation of "str" +/// @return Pointer to first unprocessed character or NULL +char *os_strptime(const char *str, const char *format, struct tm *tm) + FUNC_ATTR_NONNULL_ALL +{ +#ifdef HAVE_STRPTIME + return strptime(str, format, tm); +#else + return NULL; +#endif +} + /// Obtains the current Unix timestamp. /// /// @return Seconds since epoch. diff --git a/src/nvim/path.c b/src/nvim/path.c index 2de7e00ddb..3e1713fbdd 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -342,7 +342,7 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, p1 += utfc_ptr2len((const char_u *)p1); p2 += utfc_ptr2len((const char_u *)p2); } - return c1 - c2; + return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2; #else if (p_fic) { return mb_strnicmp((const char_u *)fname1, (const char_u *)fname2, len); diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index aef7ffa397..32c9750628 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -139,8 +139,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, cursor_col = curwin->w_wcol; } - pum_anchor_grid = (int)curwin->w_grid.handle; - if (!ui_has(kUIMultigrid)) { + pum_anchor_grid = (int)curwin->w_grid.target->handle; + pum_win_row += curwin->w_grid.row_offset; + cursor_col += curwin->w_grid.col_offset; + if (!ui_has(kUIMultigrid) && curwin->w_grid.target != &default_grid) { pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; cursor_col += curwin->w_wincol; @@ -396,7 +398,7 @@ void pum_redraw(void) char_u *p = NULL; int totwidth, width, w; int thumb_pos = 0; - int thumb_heigth = 1; + int thumb_height = 1; int round; int n; @@ -447,11 +449,11 @@ void pum_redraw(void) } if (pum_scrollbar) { - thumb_heigth = pum_height * pum_height / pum_size; - if (thumb_heigth == 0) { - thumb_heigth = 1; + thumb_height = pum_height * pum_height / pum_size; + if (thumb_height == 0) { + thumb_height = 1; } - thumb_pos = (pum_first * (pum_height - thumb_heigth) + thumb_pos = (pum_first * (pum_height - thumb_height) + (pum_size - pum_height) / 2) / (pum_size - pum_height); } @@ -614,11 +616,11 @@ void pum_redraw(void) if (pum_scrollbar > 0) { if (pum_rl) { grid_putchar(&pum_grid, ' ', row, col_off - pum_width, - i >= thumb_pos && i < thumb_pos + thumb_heigth + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } else { grid_putchar(&pum_grid, ' ', row, col_off + pum_width, - i >= thumb_pos && i < thumb_pos + thumb_heigth + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } } diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 8e86ea08c5..b7c4b6ef92 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -1,6 +1,9 @@ #ifndef NVIM_POS_H #define NVIM_POS_H +// for INT_MAX, LONG_MAX et al. +#include <limits.h> + typedef long linenr_T; // line number type /// Format used to print values which have linenr_T type #define PRIdLINENR "ld" @@ -12,8 +15,8 @@ typedef int colnr_T; /// Maximal (invalid) line number enum { MAXLNUM = 0x7fffffff }; -/// Maximal column number, 31 bits -enum { MAXCOL = 0x7fffffff }; +/// Maximal column number +enum { MAXCOL = INT_MAX }; // Minimum line number enum { MINLNUM = 1 }; // minimum column number diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a625c09f78..464d72eccb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2798,7 +2798,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, // Move the cursor to the first line in the buffer pos_T save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { + if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } @@ -3617,6 +3617,15 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (win_split(height, flags) == FAIL) { return FAIL; // not enough room for window } + + // User autocommands may have invalidated the previous window after calling + // win_split, so add a check to ensure that the win is still here + if (IS_LL_STACK(qi) && !win_valid(win)) { + // close the window that was supposed to be for the loclist + win_close(curwin, false); + return FAIL; + } + RESET_BINDING(curwin); if (IS_LL_STACK(qi)) { @@ -3665,7 +3674,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) static void qf_set_title_var(qf_list_T *qfl) { if (qfl->qf_title != NULL) { - set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title); + set_internal_string_var("w:quickfix_title", qfl->qf_title); } } @@ -4951,7 +4960,7 @@ void ex_cfile(exarg_T *eap) } } if (*eap->arg != NUL) { - set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0); } char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; @@ -5264,7 +5273,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 +5657,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/regexp.c b/src/nvim/regexp.c index a2589ac431..184f5da97d 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -5895,7 +5895,7 @@ static void regdump(char_u *pattern, bt_regprog_T *r) fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); s += 4; } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { - /* one int plus comperator */ + // one int plus comparator fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); s += 5; } @@ -6665,6 +6665,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int len = 0; /* init for GCC */ static char_u *eval_result = NULL; + // We need to keep track of how many backslashes we escape, so that the byte + // counts for `extmark_splice` are correct. + int num_escaped = 0; + // Be paranoid... if ((source == NULL && expr == NULL) || dest == NULL) { EMSG(_(e_null)); @@ -6840,6 +6844,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, // later. Used to insert a literal CR. default: if (backslash) { + num_escaped += 1; if (copy) { *dst = '\\'; } @@ -6979,7 +6984,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, *dst = NUL; exit: - return (int)((dst - dest) + 1); + return (int)((dst - dest) + 1 - num_escaped); } @@ -7139,6 +7144,7 @@ list_T *reg_submatch_list(int no) tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } + tv_list_ref(list); return list; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 8b5ee59d40..b6bcee3fda 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -559,7 +559,9 @@ static char_u *nfa_get_match_text(nfa_state_T *start) */ static void realloc_post_list(void) { - size_t new_max = (post_end - post_start) + 1000; + // For weird patterns the number of states can be very high. Increasing by + // 50% seems a reasonable compromise between memory use and speed. + const size_t new_max = (post_end - post_start) * 3 / 2; int *new_start = xrealloc(post_start, new_max * sizeof(int)); post_ptr = new_start + (post_ptr - post_start); post_end = new_start + new_max; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a78f905a70..6be3b6fb60 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -143,7 +143,7 @@ long tab_page_click_defs_size = 0; // for line_putchar. Contains the state that needs to be remembered from // putting one character to the next. typedef struct { - const char_u *p; + const char *p; int prev_c; // previous Arabic character int prev_c1; // first composing char for prev_c } LineState; @@ -232,7 +232,7 @@ void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { redraw_later(wp, NOT_VALID); - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; } } @@ -582,11 +582,18 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { - grid_invalidate(&wp->w_grid); + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); wp->w_redr_type = NOT_VALID; } + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + if (wp->w_redr_type != 0) { if (!did_one) { did_one = TRUE; @@ -774,8 +781,6 @@ static void win_update(win_T *wp, Providers *providers) type = wp->w_redr_type; - win_grid_alloc(wp); - if (type >= NOT_VALID) { wp->w_redr_status = true; wp->w_lines_valid = 0; @@ -1857,7 +1862,7 @@ static int compute_foldcolumn(win_T *wp, int col) /// Handles composing chars and arabic shaping state. static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl) { - const char_u *p = s->p; + const char_u *p = (char_u *)s->p; int cells = utf_ptr2cells(p); int c_len = utfc_ptr2len(p); int u8c, u8cc[MAX_MCO]; @@ -2077,12 +2082,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int change_start = MAXCOL; // first col of changed area int change_end = -1; // last col of changed area colnr_T trailcol = MAXCOL; // start of trailing spaces + colnr_T leadcol = 0; // start of leading spaces bool need_showbreak = false; // overlong line, skip first x chars + sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs + int num_signs; // number of signs for line int line_attr = 0; // attribute for the whole line int line_attr_lowprio = 0; // low-priority attribute for the line matchitem_T *cur; // points to the match list match_T *shl; // points to search_hl or a match - int shl_flag; // flag to indicate whether search_hl + bool shl_flag; // flag to indicate whether search_hl // has been processed or not bool prevcol_hl_flag; // flag to indicate whether prevcol // equals startcol of search_hl or one @@ -2096,6 +2104,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext + bool area_active = false; + /* draw_state: items that are drawn in sequence: */ #define WL_START 0 /* nothing done yet */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */ @@ -2315,7 +2325,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); } // do at least one character; happens when past end of line - if (fromcol == tocol) { + if (fromcol == tocol && search_match_endcol) { tocol = fromcol + 1; } area_highlighting = true; @@ -2367,11 +2377,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, wp->w_last_cursorline = wp->w_cursor.lnum; } + memset(sattrs, 0, sizeof(sattrs)); + num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); + // If this line has a sign with line highlighting set line_attr. // TODO(bfredl, vigoux): this should not take priority over decoration! - v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); - if (v != 0) { - line_attr = sign_get_attr((int)v, SIGN_LINEHL); + sign_attrs_T * sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1); + if (sattr != NULL) { + line_attr = sattr->sat_linehl; } // Highlight the current line in the quickfix window. @@ -2420,6 +2433,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, 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.lead || wp->w_p_lcs_chars.nbsp) { extra_check = true; } @@ -2431,6 +2445,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } trailcol += (colnr_T) (ptr - line); } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } } /* @@ -2656,7 +2684,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // already be in use. xfree(p_extra_free); p_extra_free = xmalloc(MAX_MCO * fdc + 1); - n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); + n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum); p_extra_free[n_extra] = NUL; p_extra = p_extra_free; c_extra = NUL; @@ -2673,7 +2701,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int count = win_signcol_count(wp); if (count > 0) { get_sign_display_info( - false, wp, lnum, row, + false, wp, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -2692,10 +2720,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // in 'lnum', then display the sign instead of the line // number. if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' - && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) { + && num_signs > 0) { int count = win_signcol_count(wp); get_sign_display_info( - true, wp, lnum, row, + true, wp, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -2745,13 +2773,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); - int num_sign = buf_getsigntype( - wp->w_buffer, lnum, SIGN_NUMHL, 0, 1); - if (num_sign != 0) { + sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); + if (num_sattr != NULL) { // :sign defined with "numhl" highlight. - char_attr = sign_get_attr(num_sign, SIGN_NUMHL); + char_attr = num_sattr->sat_numhl; } else if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { + && lnum == wp->w_cursor.lnum + && filler_todo == 0) { // When 'cursorline' is set highlight the line number of // the current line differently. // TODO(vim): Can we use CursorLine instead of CursorLineNr @@ -2850,6 +2878,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (draw_state == WL_LINE - 1 && n_extra == 0) { sign_idx = 0; draw_state = WL_LINE; + + if (has_decor && row == startrow + filler_lines) { + // hide virt_text on text hidden by 'nowrap' + decor_redraw_col(wp->w_buffer, vcol, off, true, &decor_state); + } + if (saved_n_extra) { /* Continue item from end of wrapped line. */ n_extra = saved_n_extra; @@ -2870,6 +2904,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol >= (long)wp->w_virtcol) || (number_only && draw_state > WL_NR)) && filler_todo <= 0) { + draw_virt_text(buf, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when @@ -2933,10 +2968,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vcol_prev < vcol // not at margin && vcol < tocol)) { area_attr = attr; // start highlighting + if (area_highlighting) { + area_active = true; + } } else if (area_attr != 0 && (vcol == tocol || (noinvcur && (colnr_T)vcol == wp->w_virtcol))) { area_attr = 0; // stop highlighting + area_active = false; } if (!n_extra) { @@ -2950,16 +2989,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, */ v = (long)(ptr - line); cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if (cur != NULL) { cur->pos.cur = 0; } @@ -2984,7 +3022,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, has_match_conc = v == (long)shl->startcol ? 2 : 1; match_conc = cur->conceal_char; } else { - has_match_conc = match_conc = 0; + has_match_conc = 0; } } else if (v == (long)shl->endcol) { shl->attr_cur = 0; @@ -3026,16 +3064,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, search_attr_from_match = false; search_attr = search_hl.attr_cur; cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if (shl->attr_cur != 0) { search_attr = shl->attr_cur; search_attr_from_match = shl != &search_hl; @@ -3048,6 +3085,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && (wp->w_p_list && lcs_eol_one == -1)) { search_attr = 0; } + + // Do not allow a conceal over EOL otherwise EOL will be missed + // and bad things happen. + if (*ptr == NUL) { + has_match_conc = 0; + } } if (diff_hlf != (hlf_T)0) { @@ -3115,6 +3158,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, mb_utf8 = false; } } else { + assert(p_extra != NULL); c = *p_extra; mb_c = c; // If the UTF-8 character is more than one byte: @@ -3392,9 +3436,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_attr = hl_combine_attr(spell_attr, char_attr); } + if (wp->w_buffer->terminal) { + char_attr = hl_combine_attr(term_attrs[vcol], char_attr); + } + if (has_decor && v > 0) { - int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, - &decor_state); + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, off, + selected, &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3404,10 +3454,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (wp->w_buffer->terminal) { - char_attr = hl_combine_attr(term_attrs[vcol], char_attr); - } - // Found last space before word: check for line break. if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak((int)(*ptr))) { @@ -3416,8 +3462,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // TODO: is passing p for start of the line OK? n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; if (c == TAB && n_extra + col > grid->Columns) { - n_extra = (int)wp->w_buffer->b_p_ts - - vcol % (int)wp->w_buffer->b_p_ts - 1; + n_extra = tabstop_padding(vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; } c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; c_final = NUL; @@ -3437,6 +3483,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, || (mb_utf8 && (mb_c == 160 || mb_c == 0x202f))) && curwin->w_p_lcs_chars.nbsp) || (c == ' ' && curwin->w_p_lcs_chars.space + && ptr - line >= leadcol && ptr - line <= trailcol))) { c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; n_attr = 1; @@ -3452,8 +3499,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') { - c = wp->w_p_lcs_chars.trail; + if ((trailcol != MAXCOL && ptr > line + trailcol && c == ' ') + || (leadcol != 0 && ptr < line + leadcol && c == ' ')) { + c = (ptr > line + trailcol) ? wp->w_p_lcs_chars.trail + : wp->w_p_lcs_chars.lead; n_attr = 1; extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr @@ -3483,8 +3532,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, vcol_adjusted = vcol - MB_CHARLEN(p_sbr); } // tab amount depends on current column - tab_len = (int)wp->w_buffer->b_p_ts - - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1; + tab_len = tabstop_padding(vcol_adjusted, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; if (!wp->w_p_lbr || !wp->w_p_list) { n_extra = tab_len; @@ -3517,6 +3567,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, xfree(p_extra_free); p_extra_free = p; for (i = 0; i < tab_len; i++) { + if (*p == NUL) { + tab_len = i; + break; + } int lcs = wp->w_p_lcs_chars.tab2; // if tab3 is given, need to change the char @@ -3672,12 +3726,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { char_attr = conceal_attr; if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1) - && (syn_get_sub_char() != NUL || match_conc + && (syn_get_sub_char() != NUL + || (has_match_conc && match_conc) || wp->w_p_cole == 1) && wp->w_p_cole != 3) { // First time at this concealed item: display one // character. - if (match_conc) { + if (has_match_conc && match_conc) { c = match_conc; } else if (syn_get_sub_char() != NUL) { c = syn_get_sub_char(); @@ -3837,16 +3892,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // 'search_hl' and the match list. char_attr = search_hl.attr; cur = wp->w_match_head; - shl_flag = FALSE; - while (cur != NULL || shl_flag == FALSE) { - if (shl_flag == FALSE - && ((cur != NULL - && cur->priority > SEARCH_HL_PRIORITY) - || cur == NULL)) { + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = &search_hl; - shl_flag = TRUE; - } else + shl_flag = true; + } else { shl = &cur->hl; + } if ((ptr - line) - 1 == (long)shl->startcol && (shl == &search_hl || !shl->is_addpos)) { char_attr = shl->attr; @@ -3897,9 +3951,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); - if (vp) { - virt_text = *vp; + virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr); + if (kv_size(virt_text)) { do_virttext = true; } } @@ -3915,7 +3968,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int i; size_t virt_pos = 0; - LineState s = LINE_STATE((char_u *)""); + LineState s = LINE_STATE(""); int virt_attr = 0; // Make sure alignment is the same regardless @@ -3959,7 +4012,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (do_virttext && !delay_virttext) { if (*s.p == NUL) { if (virt_pos < virt_text.size) { - s.p = (char_u *)kv_A(virt_text, virt_pos).text; + s.p = kv_A(virt_text, virt_pos).text; int hl_id = kv_A(virt_text, virt_pos).hl_id; virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; virt_pos++; @@ -4025,6 +4078,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, col += n; } } + + draw_virt_text(buf, &col, grid->Columns); grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); row++; @@ -4243,7 +4298,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, && (grid->Columns == Columns // Window spans the width of the screen, || ui_has(kUIMultigrid)) // or has dedicated grid. && !wp->w_p_rl; // Not right-to-left. - grid_put_linebuf(grid, row, 0, col - boguscols, grid->Columns, wp->w_p_rl, + + int draw_col = col - boguscols; + draw_virt_text(buf, &draw_col, grid->Columns); + grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, wrap); if (wrap) { ScreenGrid *current_grid = grid; @@ -4319,6 +4377,56 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, return row; } +void draw_virt_text(buf_T *buf, int *end_col, int max_col) +{ + DecorState *state = &decor_state; + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->start_row == state->row && kv_size(item->decor.virt_text) + && item->decor.virt_text_pos == kVTOverlay + && item->virt_col >= 0) { + VirtText vt = item->decor.virt_text; + HlMode hl_mode = item->decor.hl_mode; + LineState s = LINE_STATE(""); + int virt_attr = 0; + int col = item->virt_col; + size_t virt_pos = 0; + item->virt_col = -2; // deactivate + + while (col < max_col) { + if (!*s.p) { + if (virt_pos == kv_size(vt)) { + break; + } + s.p = kv_A(vt, virt_pos).text; + int hl_id = kv_A(vt, virt_pos).hl_id; + virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; + virt_pos++; + continue; + } + int attr; + bool through = false; + if (hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (hl_mode == kHlModeBlend) { + through = (*s.p == ' '); + attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); + } else { + attr = virt_attr; + } + schar_T dummy[2]; + int cells = line_putchar(&s, through ? dummy : &linebuf_char[col], + max_col-col, false); + linebuf_attr[col++] = attr; + if (cells > 1) { + linebuf_attr[col++] = attr; + } + } + *end_col = MAX(*end_col, col); + } + } +} + /// Determine if dedicated window grid should be used or the default_grid /// /// If UI did not request multigrid support, draw all windows on the @@ -4331,14 +4439,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, /// screen positions. void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { - if (!(*grid)->chars && *grid != &default_grid) { - *row_off += (*grid)->row_offset; - *col_off += (*grid)->col_offset; - if (*grid == &msg_grid_adj && msg_grid.chars) { - *grid = &msg_grid; - } else { - *grid = &default_grid; - } + if ((*grid)->target) { + *row_off += (*grid)->row_offset; + *col_off += (*grid)->col_offset; + *grid = (*grid)->target; } } @@ -4352,7 +4456,7 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) static void get_sign_display_info( bool nrcol, win_T *wp, - linenr_T lnum, + sign_attrs_T sattrs[], int row, int startrow, int filler_lines, @@ -4369,8 +4473,6 @@ static void get_sign_display_info( int *sign_idxp ) { - int text_sign; - // Draw cells with the sign value or blank. *c_extrap = ' '; *c_finalp = NUL; @@ -4382,10 +4484,9 @@ static void get_sign_display_info( } if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, - *sign_idxp, count); - if (text_sign != 0) { - *pp_extra = sign_get_text(text_sign); + sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, *sign_idxp, count); + if (sattr != NULL) { + *pp_extra = sattr->sat_text; if (*pp_extra != NULL) { *c_extrap = NUL; *c_finalp = NUL; @@ -4418,7 +4519,7 @@ static void get_sign_display_info( (*pp_extra)[*n_extrap] = NUL; } } - *char_attrp = sign_get_attr(text_sign, SIGN_TEXT); + *char_attrp = sattr->sat_texthl; } } @@ -5092,9 +5193,9 @@ static void redraw_custom_statusline(win_T *wp) // When there is an error disable the statusline, otherwise the // display is messed up with errors and a redraw triggers the problem // again and again. - set_string_option_direct((char_u *)"statusline", -1, - (char_u *)"", OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); + set_string_option_direct("statusline", -1, (char_u *)"", + OPT_FREE | (*wp->w_p_stl != NUL + ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); } did_emsg |= saved_did_emsg; entered = false; @@ -5175,7 +5276,7 @@ get_keymap_str ( static void win_redr_custom ( win_T *wp, - int draw_ruler /* TRUE or FALSE */ + bool draw_ruler ) { static int entered = FALSE; @@ -5342,6 +5443,64 @@ theend: entered = FALSE; } +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + int *attrs = wp->w_float_config.border_attr; + + + int *adj = wp->w_border_adj; + int irow = wp->w_height_inner, icol = wp->w_width_inner; + + if (adj[0]) { + grid_puts_line_start(grid, 0); + if (adj[3]) { + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + } + for (int i = 0; i < icol; i++) { + grid_put_schar(grid, 0, i+adj[3], chars[1], attrs[1]); + } + if (adj[1]) { + grid_put_schar(grid, 0, icol+adj[3], chars[2], attrs[2]); + } + grid_puts_line_flush(false); + } + + for (int i = 0; i < irow; i++) { + if (adj[3]) { + grid_puts_line_start(grid, i+adj[0]); + grid_put_schar(grid, i+adj[0], 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + } + if (adj[1]) { + int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; + grid_puts_line_start(grid, i+adj[0]); + grid_put_schar(grid, i+adj[0], icol+adj[3], chars[ic], attrs[ic]); + grid_puts_line_flush(false); + } + } + + if (adj[2]) { + grid_puts_line_start(grid, irow+adj[0]); + if (adj[3]) { + grid_put_schar(grid, irow+adj[0], 0, chars[6], attrs[6]); + } + for (int i = 0; i < icol; i++) { + int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; + grid_put_schar(grid, irow+adj[0], i+adj[3], chars[ic], attrs[ic]); + } + grid_put_schar(grid, irow+adj[0], icol+adj[3], chars[4], attrs[4]); + grid_puts_line_flush(false); + } +} + // Low-level functions to manipulate invidual character cells on the // screen grid. @@ -5479,6 +5638,20 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } +void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +{ + assert(put_dirty_row == row); + unsigned int off = grid->line_offset[row] + col; + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + schar_copy(grid->chars[off], schar); + grid->attrs[off] = attr; + + put_dirty_first = MIN(put_dirty_first, col); + // TODO(bfredl): Y U NO DOUBLEWIDTH? + put_dirty_last = MAX(put_dirty_last, col+1); + } +} + /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, @@ -6054,7 +6227,7 @@ void check_for_delay(int check_msg_scroll) && !did_wait_return && emsg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1006L, true); emsg_on_display = false; if (check_msg_scroll) { msg_scroll = false; @@ -6070,12 +6243,15 @@ void check_for_delay(int check_msg_scroll) void win_grid_alloc(win_T *wp) { ScreenGrid *grid = &wp->w_grid; + ScreenGrid *grid_allocated = &wp->w_grid_alloc; int rows = wp->w_height_inner; int cols = wp->w_width_inner; + int total_rows = wp->w_height_outer; + int total_cols = wp->w_width_outer; bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; - bool has_allocation = (grid->chars != NULL); + bool has_allocation = (grid_allocated->chars != NULL); if (grid->Rows != rows) { wp->w_lines_valid = 0; @@ -6084,35 +6260,47 @@ void win_grid_alloc(win_T *wp) } int was_resized = false; - if ((has_allocation != want_allocation) - || grid->Rows != rows - || grid->Columns != cols) { - if (want_allocation) { - grid_alloc(grid, rows, cols, wp->w_grid.valid, false); - grid->valid = true; - } else { - // Single grid mode, all rendering will be redirected to default_grid. - // Only keep track of the size and offset of the window. - grid_free(grid); - grid->Rows = rows; - grid->Columns = cols; - grid->valid = false; + if (want_allocation && (!has_allocation + || grid_allocated->Rows != total_rows + || grid_allocated->Columns != total_cols)) { + grid_alloc(grid_allocated, total_rows, total_cols, + wp->w_grid_alloc.valid, false); + grid_allocated->valid = true; + if (wp->w_floating && wp->w_float_config.border) { + wp->w_redr_border = true; } was_resized = true; - } else if (want_allocation && has_allocation && !wp->w_grid.valid) { - grid_invalidate(grid); - grid->valid = true; + } else if (!want_allocation && has_allocation) { + // Single grid mode, all rendering will be redirected to default_grid. + // Only keep track of the size and offset of the window. + grid_free(grid_allocated); + grid_allocated->valid = false; + was_resized = true; + } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) { + grid_invalidate(grid_allocated); + grid_allocated->valid = true; } - grid->row_offset = wp->w_winrow; - grid->col_offset = wp->w_wincol; + grid->Rows = rows; + grid->Columns = cols; + + if (want_allocation) { + grid->target = grid_allocated; + grid->row_offset = wp->w_border_adj[0]; + grid->col_offset = wp->w_border_adj[3]; + } else { + grid->target = &default_grid; + grid->row_offset = wp->w_winrow; + grid->col_offset = wp->w_wincol; + } // send grid resize event if: // - a grid was just resized // - screen_resize was called and all grid sizes must be sent // - the UI wants multigrid event (necessary) if ((send_grid_resize || was_resized) && want_allocation) { - ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); + ui_call_grid_resize(grid_allocated->handle, + grid_allocated->Columns, grid_allocated->Rows); } } @@ -6201,6 +6389,9 @@ retry: tab_page_click_defs = new_tab_page_click_defs; tab_page_click_defs_size = Columns; + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + default_grid.row_offset = 0; default_grid.col_offset = 0; default_grid.handle = DEFAULT_GRID_HANDLE; @@ -6852,7 +7043,7 @@ void draw_tabline(void) did_emsg = false; win_redr_custom(NULL, false); if (did_emsg) { - set_string_option_direct((char_u *)"tabline", -1, + set_string_option_direct("tabline", -1, (char_u *)"", OPT_FREE, SID_ERROR); } did_emsg |= saved_did_emsg; @@ -7105,20 +7296,23 @@ static void win_redr_ruler(win_T *wp, int always) if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) return; - /* Don't draw the ruler while doing insert-completion, it might overwrite - * the (long) mode message. */ - if (wp == lastwin && lastwin->w_status_height == 0) - if (edit_submode != NULL) + // Don't draw the ruler while doing insert-completion, it might overwrite + // the (long) mode message. + if (wp == lastwin && lastwin->w_status_height == 0) { + if (edit_submode != NULL) { return; + } + } if (*p_ruf) { int save_called_emsg = called_emsg; - called_emsg = FALSE; - win_redr_custom(wp, TRUE); - if (called_emsg) - set_string_option_direct((char_u *)"rulerformat", -1, - (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg = false; + win_redr_custom(wp, true); + if (called_emsg) { + set_string_option_direct("rulerformat", -1, (char_u *)"", + OPT_FREE, SID_ERROR); + } called_emsg |= save_called_emsg; return; } @@ -7334,6 +7528,10 @@ void screen_resize(int width, int height) Rows = height; Columns = width; check_shellsize(); + int max_p_ch = Rows - min_rows() + 1; + if (!ui_has(kUIMessages) && p_ch > max_p_ch) { + p_ch = max_p_ch ? max_p_ch : 1; + } height = Rows; width = Columns; p_lines = Rows; @@ -7436,8 +7634,9 @@ void win_new_shellsize(void) static long old_Columns = 0; if (old_Rows != Rows) { - // if 'window' uses the whole screen, keep it using that */ - if (p_window == old_Rows - 1 || old_Rows == 0) { + // If 'window' uses the whole screen, keep it using that. + // Don't change it when set with "-w size" on the command line. + if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) { p_window = Rows - 1; } old_Rows = Rows; @@ -7452,7 +7651,7 @@ void win_new_shellsize(void) win_T *get_win_by_grid_handle(handle_T handle) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_grid.handle == handle) { + if (wp->w_grid_alloc.handle == handle) { return wp; } } diff --git a/src/nvim/search.c b/src/nvim/search.c index 2802da6f7f..abe05bbd12 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1021,8 +1021,9 @@ static int first_submatch(regmmatch_T *rp) * Return 0 for failure, 1 for found, 2 for found and line offset added. */ int do_search( - oparg_T *oap, /* can be NULL */ - int dirc, /* '/' or '?' */ + oparg_T *oap, // can be NULL + int dirc, // '/' or '?' + int search_delim, // delimiter for search, e.g. '%' in s%regex%replacement char_u *pat, long count, int options, @@ -1101,8 +1102,8 @@ int do_search( searchstr = pat; dircp = NULL; - /* use previous pattern */ - if (pat == NULL || *pat == NUL || *pat == dirc) { + // use previous pattern + if (pat == NULL || *pat == NUL || *pat == search_delim) { if (spats[RE_SEARCH].pat == NULL) { // no previous pattern searchstr = spats[RE_SUBST].pat; if (searchstr == NULL) { @@ -1122,15 +1123,15 @@ int do_search( * If there is a matching '/' or '?', toss it. */ ps = strcopy; - p = skip_regexp(pat, dirc, p_magic, &strcopy); + p = skip_regexp(pat, search_delim, p_magic, &strcopy); if (strcopy != ps) { /* made a copy of "pat" to change "\?" to "?" */ searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); pat = strcopy; searchstr = strcopy; } - if (*p == dirc) { - dircp = p; /* remember where we put the NUL */ + if (*p == search_delim) { + dircp = p; // remember where we put the NUL *p++ = NUL; } spats[0].off.line = FALSE; @@ -1320,7 +1321,7 @@ int do_search( RE_LAST, sia); if (dircp != NULL) { - *dircp = dirc; // restore second '/' or '?' for normal_cmd() + *dircp = search_delim; // restore second '/' or '?' for normal_cmd() } if (!shortmess(SHM_SEARCH) @@ -1400,6 +1401,7 @@ int do_search( } dirc = *++pat; + search_delim = dirc; if (dirc != '?' && dirc != '/') { retval = 0; EMSG(_("E386: Expected '?' or '/' after ';'")); @@ -2326,6 +2328,9 @@ showmatch( return; } } + if (*p == NUL) { + return; + } if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep vim_beep(BO_MATCH); @@ -2368,10 +2373,11 @@ showmatch( * brief pause, unless 'm' is present in 'cpo' and a character is * available. */ - if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) - os_delay(p_mat * 100L, true); - else if (!char_avail()) - os_delay(p_mat * 100L, false); + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { + os_delay(p_mat * 100L + 8, true); + } else if (!char_avail()) { + os_delay(p_mat * 100L + 9, false); + } curwin->w_cursor = save_cursor; // restore cursor position *so = save_so; *siso = save_siso; @@ -2685,8 +2691,9 @@ fwd_word( while (--count >= 0) { /* When inside a range of folded lines, move to the last char of the * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } sclass = cls(); /* @@ -2803,8 +2810,9 @@ int end_word(long count, int bigword, int stop, int empty) while (--count >= 0) { /* When inside a range of folded lines, move to the last char of the * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) - coladvance((colnr_T)MAXCOL); + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } sclass = cls(); if (inc_cursor() == -1) return FAIL; 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/sign.c b/src/nvim/sign.c index fc9f53c192..5c7b497a19 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -18,6 +18,7 @@ #include "nvim/move.h" #include "nvim/screen.h" #include "nvim/syntax.h" +#include "nvim/option.h" /// Struct to hold the sign properties. typedef struct sign sign_T; @@ -83,13 +84,13 @@ static signgroup_T * sign_group_ref(const char_u *groupname) group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); STRCPY(group->sg_name, groupname); - group->refcount = 1; - group->next_sign_id = 1; + group->sg_refcount = 1; + group->sg_next_sign_id = 1; hash_add_item(&sg_table, hi, group->sg_name, hash); } else { // existing group group = HI2SG(hi); - group->refcount++; + group->sg_refcount++; } return group; @@ -105,8 +106,8 @@ static void sign_group_unref(char_u *groupname) hi = hash_find(&sg_table, groupname); if (!HASHITEM_EMPTY(hi)) { group = HI2SG(hi); - group->refcount--; - if (group->refcount == 0) { + group->sg_refcount--; + if (group->sg_refcount == 0) { // All the signs in this group are removed hash_remove(&sg_table, hi); xfree(group); @@ -117,12 +118,12 @@ static void sign_group_unref(char_u *groupname) /// Returns TRUE if 'sign' is in 'group'. /// A sign can either be in the global group (sign->group == NULL) /// or in a named group. If 'group' is '*', then the sign is part of the group. -int sign_in_group(signlist_T *sign, const char_u *group) +int sign_in_group(sign_entry_T *sign, const char_u *group) { return ((group != NULL && STRCMP(group, "*") == 0) - || (group == NULL && sign->group == NULL) - || (group != NULL && sign->group != NULL - && STRCMP(group, sign->group->sg_name) == 0)); + || (group == NULL && sign->se_group == NULL) + || (group != NULL && sign->se_group != NULL + && STRCMP(group, sign->se_group->sg_name) == 0)); } /// Get the next free sign identifier in the specified group @@ -130,7 +131,7 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) { int id = 1; signgroup_T *group = NULL; - signlist_T *sign; + sign_entry_T *sign; hashitem_T *hi; int found = false; @@ -147,13 +148,13 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) if (group == NULL) { id = next_sign_id++; // global group } else { - id = group->next_sign_id++; + id = group->sg_next_sign_id++; } // Check whether this sign is already placed in the buffer found = true; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (id == sign->id && sign_in_group(sign, groupname)) { + if (id == sign->se_id && sign_in_group(sign, groupname)) { found = false; // sign identifier is in use break; } @@ -167,8 +168,8 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) /// 'next' signs. static void insert_sign( buf_T *buf, // buffer to store sign in - signlist_T *prev, // previous sign entry - signlist_T *next, // next sign entry + sign_entry_T *prev, // previous sign entry + sign_entry_T *next, // next sign entry int id, // sign ID const char_u *group, // sign group; NULL for global group int prio, // sign priority @@ -177,21 +178,21 @@ static void insert_sign( bool has_text_or_icon // sign has text or icon ) { - signlist_T *newsign = xmalloc(sizeof(signlist_T)); - newsign->id = id; - newsign->lnum = lnum; - newsign->typenr = typenr; - newsign->has_text_or_icon = has_text_or_icon; + sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T)); + newsign->se_id = id; + newsign->se_lnum = lnum; + newsign->se_typenr = typenr; + newsign->se_has_text_or_icon = has_text_or_icon; if (group != NULL) { - newsign->group = sign_group_ref(group); + newsign->se_group = sign_group_ref(group); } else { - newsign->group = NULL; + newsign->se_group = NULL; } - newsign->priority = prio; - newsign->next = next; - newsign->prev = prev; + newsign->se_priority = prio; + newsign->se_next = next; + newsign->se_prev = prev; if (next != NULL) { - next->prev = newsign; + next->se_prev = newsign; } buf->b_signcols_max = -1; @@ -206,14 +207,14 @@ static void insert_sign( // first sign in signlist buf->b_signlist = newsign; } else { - prev->next = newsign; + prev->se_next = newsign; } } /// Insert a new sign sorted by line number and sign priority. static void insert_sign_by_lnum_prio( buf_T *buf, // buffer to store sign in - signlist_T *prev, // previous sign entry + sign_entry_T *prev, // previous sign entry int id, // sign ID const char_u *group, // sign group; NULL for global group int prio, // sign priority @@ -222,19 +223,19 @@ static void insert_sign_by_lnum_prio( bool has_text_or_icon // sign has text or icon ) { - signlist_T *sign; + sign_entry_T *sign; // keep signs sorted by lnum, priority and id: insert new sign at // the proper position in the list for this lnum. - while (prev != NULL && prev->lnum == lnum - && (prev->priority < prio - || (prev->priority == prio && prev->id <= id))) { - prev = prev->prev; + while (prev != NULL && prev->se_lnum == lnum + && (prev->se_priority < prio + || (prev->se_priority == prio && prev->se_id <= id))) { + prev = prev->se_prev; } if (prev == NULL) { sign = buf->b_signlist; } else { - sign = prev->next; + sign = prev->se_next; } insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon); @@ -254,16 +255,16 @@ char_u * sign_typenr2name(int typenr) } /// Return information about a sign in a Dict -dict_T * sign_get_info(signlist_T *sign) +dict_T * sign_get_info(sign_entry_T *sign) { dict_T *d = tv_dict_alloc(); - tv_dict_add_nr(d, S_LEN("id"), sign->id); - tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL) + tv_dict_add_nr(d, S_LEN("id"), sign->se_id); + tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL) ? (char *)"" - : (char *)sign->group->sg_name)); - tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum); - tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->typenr)); - tv_dict_add_nr(d, S_LEN("priority"), sign->priority); + : (char *)sign->se_group->sg_name)); + tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum); + tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->se_typenr)); + tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority); return d; } @@ -271,17 +272,17 @@ dict_T * sign_get_info(signlist_T *sign) // Sort the signs placed on the same line as "sign" by priority. Invoked after // changing the priority of an already placed sign. Assumes the signs in the // buffer are sorted by line number and priority. -static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) +static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) FUNC_ATTR_NONNULL_ALL { // If there is only one sign in the buffer or only one sign on the line or // the sign is already sorted by priority, then return. - if ((sign->prev == NULL - || sign->prev->lnum != sign->lnum - || sign->prev->priority > sign->priority) - && (sign->next == NULL - || sign->next->lnum != sign->lnum - || sign->next->priority < sign->priority)) { + if ((sign->se_prev == NULL + || sign->se_prev->se_lnum != sign->se_lnum + || sign->se_prev->se_priority > sign->se_priority) + && (sign->se_next == NULL + || sign->se_next->se_lnum != sign->se_lnum + || sign->se_next->se_priority < sign->se_priority)) { return; } @@ -289,55 +290,55 @@ static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) // Find a sign after which 'sign' should be inserted // First search backward for a sign with higher priority on the same line - signlist_T *p = sign; - while (p->prev != NULL - && p->prev->lnum == sign->lnum - && p->prev->priority <= sign->priority) { - p = p->prev; + sign_entry_T *p = sign; + while (p->se_prev != NULL + && p->se_prev->se_lnum == sign->se_lnum + && p->se_prev->se_priority <= sign->se_priority) { + p = p->se_prev; } if (p == sign) { // Sign not found. Search forward for a sign with priority just before // 'sign'. - p = sign->next; - while (p->next != NULL - && p->next->lnum == sign->lnum - && p->next->priority > sign->priority) { - p = p->next; + p = sign->se_next; + while (p->se_next != NULL + && p->se_next->se_lnum == sign->se_lnum + && p->se_next->se_priority > sign->se_priority) { + p = p->se_next; } } // Remove 'sign' from the list if (buf->b_signlist == sign) { - buf->b_signlist = sign->next; + buf->b_signlist = sign->se_next; } - if (sign->prev != NULL) { - sign->prev->next = sign->next; + if (sign->se_prev != NULL) { + sign->se_prev->se_next = sign->se_next; } - if (sign->next != NULL) { - sign->next->prev = sign->prev; + if (sign->se_next != NULL) { + sign->se_next->se_prev = sign->se_prev; } - sign->prev = NULL; - sign->next = NULL; + sign->se_prev = NULL; + sign->se_next = NULL; // Re-insert 'sign' at the right place - if (p->priority <= sign->priority) { + if (p->se_priority <= sign->se_priority) { // 'sign' has a higher priority and should be inserted before 'p' - sign->prev = p->prev; - sign->next = p; - p->prev = sign; - if (sign->prev != NULL) { - sign->prev->next = sign; + sign->se_prev = p->se_prev; + sign->se_next = p; + p->se_prev = sign; + if (sign->se_prev != NULL) { + sign->se_prev->se_next = sign; } if (buf->b_signlist == p) { buf->b_signlist = sign; } } else { // 'sign' has a lower priority and should be inserted after 'p' - sign->prev = p; - sign->next = p->next; - p->next = sign; - if (sign->next != NULL) { - sign->next->prev = sign; + sign->se_prev = p; + sign->se_next = p->se_next; + p->se_next = sign; + if (sign->se_next != NULL) { + sign->se_next->se_prev = sign; } } } @@ -354,19 +355,19 @@ void buf_addsign( bool has_text_or_icon // sign has text or icon ) { - signlist_T *sign; // a sign in the signlist - signlist_T *prev; // the previous sign + sign_entry_T *sign; // a sign in the signlist + sign_entry_T *prev; // the previous sign prev = NULL; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (lnum == sign->lnum && id == sign->id + if (lnum == sign->se_lnum && id == sign->se_id && sign_in_group(sign, groupname)) { // Update an existing sign - sign->typenr = typenr; - sign->priority = prio; + sign->se_typenr = typenr; + sign->se_priority = prio; sign_sort_by_prio_on_line(buf, sign); return; - } else if (lnum < sign->lnum) { + } else if (lnum < sign->se_lnum) { insert_sign_by_lnum_prio( buf, prev, @@ -398,69 +399,119 @@ linenr_T buf_change_sign_type( buf_T *buf, // buffer to store sign in int markId, // sign ID const char_u *group, // sign group - int typenr // typenr of sign we are adding + int typenr, // typenr of sign we are adding + int prio // sign priority ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->id == markId && sign_in_group(sign, group)) { - sign->typenr = typenr; - return sign->lnum; + if (sign->se_id == markId && sign_in_group(sign, group)) { + sign->se_typenr = typenr; + sign->se_priority = prio; + sign_sort_by_prio_on_line(buf, sign); + return sign->se_lnum; } } return (linenr_T)0; } -/// Gets a sign from a given line. -/// -/// Return the type number of the sign at line number 'lnum' in buffer 'buf' -/// which has the attribute specified by 'type'. Returns 0 if a sign is not -/// found at the line number or it doesn't have the specified attribute. -/// @param buf Buffer in which to search -/// @param lnum Line in which to search +/// Return the sign attrs which has the attribute specified by 'type'. Returns +/// NULL if a sign is not found with the specified attribute. /// @param type Type of sign to look for +/// @param sattrs Sign attrs to search through /// @param idx if there multiple signs, this index will pick the n-th -// out of the most `max_signs` sorted ascending by Id. +/// out of the most `max_signs` sorted ascending by Id. /// @param max_signs the number of signs, with priority for the ones -// with the highest Ids. -/// @return Identifier of the matching sign, or 0 -int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, - int idx, int max_signs) +/// with the highest Ids. +/// @return Attrs of the matching sign, or NULL +sign_attrs_T * sign_get_attr(SignType type, sign_attrs_T sattrs[], + int idx, int max_signs) { - signlist_T *sign; // a sign in a b_signlist - signlist_T *matches[9]; + sign_attrs_T *matches[SIGN_SHOW_MAX]; int nr_matches = 0; - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum == lnum - && (type == SIGN_ANY - || (type == SIGN_TEXT - && sign_get_text(sign->typenr) != NULL) - || (type == SIGN_LINEHL - && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) - || (type == SIGN_NUMHL - && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { - matches[nr_matches] = sign; + for (int i = 0; i < SIGN_SHOW_MAX; i++) { + if ( (type == SIGN_TEXT && sattrs[i].sat_text != NULL) + || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0) + || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) { + matches[nr_matches] = &sattrs[i]; nr_matches++; - // signlist is sorted with most important (priority, id), thus we + // attr list is sorted with most important (priority, id), thus we // may stop as soon as we have max_signs matches - if (nr_matches == ARRAY_SIZE(matches) || nr_matches >= max_signs) { + if (nr_matches >= max_signs) { break; } } } - if (nr_matches > 0) { - if (idx >= nr_matches) { - return 0; - } + if (nr_matches > idx) { + return matches[nr_matches - idx - 1]; + } + + return NULL; +} + +/// Lookup a sign by typenr. Returns NULL if sign is not found. +static sign_T * find_sign_by_typenr(int typenr) +{ + sign_T *sp; - return matches[nr_matches - idx -1]->typenr; + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp; + } } + return NULL; +} - return 0; +/// Return the attributes of all the signs placed on line 'lnum' in buffer +/// 'buf'. Used when refreshing the screen. Returns the number of signs. +/// @param buf Buffer in which to search +/// @param lnum Line in which to search +/// @param sattrs Output array for attrs +/// @return Number of signs of which attrs were found +int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) +{ + sign_entry_T *sign; + sign_T *sp; + + int nr_matches = 0; + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->se_lnum > lnum) { + // Signs are sorted by line number in the buffer. No need to check + // for signs after the specified line number 'lnum'. + break; + } + + if (sign->se_lnum == lnum) { + sign_attrs_T sattr; + memset(&sattr, 0, sizeof(sattr)); + sattr.sat_typenr = sign->se_typenr; + sp = find_sign_by_typenr(sign->se_typenr); + if (sp != NULL) { + sattr.sat_text = sp->sn_text; + if (sattr.sat_text != NULL && sp->sn_text_hl != 0) { + sattr.sat_texthl = syn_id2attr(sp->sn_text_hl); + } + if (sp->sn_line_hl != 0) { + sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); + } + if (sp->sn_num_hl != 0) { + sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); + } + } + + sattrs[nr_matches] = sattr; + nr_matches++; + if (nr_matches == SIGN_SHOW_MAX) { + break; + } + } + } + return nr_matches; } /// Delete sign 'id' in group 'group' from buffer 'buf'. @@ -478,26 +529,26 @@ linenr_T buf_delsign( char_u *group // sign group ) { - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *sign; // a sign in a b_signlist - signlist_T *next; // the next sign in a b_signlist + sign_entry_T **lastp; // pointer to pointer to current sign + sign_entry_T *sign; // a sign in a b_signlist + sign_entry_T *next; // the next sign in a b_signlist linenr_T lnum; // line number whose sign was deleted buf->b_signcols_max = -1; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->next; - if ((id == 0 || sign->id == id) - && (atlnum == 0 || sign->lnum == atlnum) + next = sign->se_next; + if ((id == 0 || sign->se_id == id) + && (atlnum == 0 || sign->se_lnum == atlnum) && sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { - next->prev = sign->prev; + next->se_prev = sign->se_prev; } - lnum = sign->lnum; - if (sign->group != NULL) { - sign_group_unref(sign->group->sg_name); + lnum = sign->se_lnum; + if (sign->se_group != NULL) { + sign_group_unref(sign->se_group->sg_name); } xfree(sign); redraw_buf_line_later(buf, lnum); @@ -511,7 +562,7 @@ linenr_T buf_delsign( break; } } else { - lastp = &sign->next; + lastp = &sign->se_next; } } @@ -535,11 +586,11 @@ int buf_findsign( char_u *group // sign group ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->id == id && sign_in_group(sign, group)) { - return (int)sign->lnum; + if (sign->se_id == id && sign_in_group(sign, group)) { + return (int)sign->se_lnum; } } @@ -548,16 +599,22 @@ int buf_findsign( /// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is /// not found at the line. If 'groupname' is NULL, searches in the global group. -static signlist_T * buf_getsign_at_line( +static sign_entry_T * buf_getsign_at_line( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum, // line number of sign char_u *groupname // sign group name ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum == lnum && sign_in_group(sign, groupname)) { + if (sign->se_lnum > lnum) { + // Signs are sorted by line number in the buffer. No need to check + // for signs after the specified line number 'lnum'. + break; + } + + if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) { return sign; } } @@ -572,11 +629,11 @@ int buf_findsign_id( char_u *groupname // sign group name ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist sign = buf_getsign_at_line(buf, lnum, groupname); if (sign != NULL) { - return sign->id; + return sign->se_id; } return 0; @@ -585,9 +642,9 @@ int buf_findsign_id( /// Delete signs in buffer "buf". void buf_delete_signs(buf_T *buf, char_u *group) { - signlist_T *sign; - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *next; + sign_entry_T *sign; + sign_entry_T **lastp; // pointer to pointer to current sign + sign_entry_T *next; // When deleting the last sign need to redraw the windows to remove the // sign column. Not when curwin is NULL (this means we're exiting). @@ -597,18 +654,18 @@ void buf_delete_signs(buf_T *buf, char_u *group) lastp = &buf->b_signlist; for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->next; + next = sign->se_next; if (sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { - next->prev = sign->prev; + next->se_prev = sign->se_prev; } - if (sign->group != NULL) { - sign_group_unref(sign->group->sg_name); + if (sign->se_group != NULL) { + sign_group_unref(sign->se_group->sg_name); } xfree(sign); } else { - lastp = &sign->next; + lastp = &sign->se_next; } } buf->b_signcols_max = -1; @@ -618,7 +675,7 @@ void buf_delete_signs(buf_T *buf, char_u *group) void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; - signlist_T *sign; + sign_entry_T *sign; char lbuf[MSG_BUF_LEN]; char group[MSG_BUF_LEN]; @@ -642,16 +699,16 @@ void sign_list_placed(buf_T *rbuf, char_u *sign_group) if (!sign_in_group(sign, sign_group)) { continue; } - if (sign->group != NULL) { + if (sign->se_group != NULL) { vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"), - sign->group->sg_name); + sign->se_group->sg_name); } else { group[0] = '\0'; } vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%ld id=%d%s name=%s priority=%d"), - (long)sign->lnum, sign->id, group, - sign_typenr2name(sign->typenr), sign->priority); + (long)sign->se_lnum, sign->se_id, group, + sign_typenr2name(sign->se_typenr), sign->se_priority); MSG_PUTS(lbuf); msg_putchar('\n'); } @@ -670,26 +727,43 @@ void sign_mark_adjust( long amount_after ) { - signlist_T *sign; // a sign in a b_signlist - linenr_T new_lnum; // new line number to assign to sign + sign_entry_T *sign; // a sign in a b_signlist + sign_entry_T *next; // the next sign in a b_signlist + sign_entry_T *last = NULL; // pointer to pointer to current sign + sign_entry_T **lastp = NULL; // pointer to pointer to current sign + linenr_T new_lnum; // new line number to assign to sign + int is_fixed = 0; + int signcol = win_signcol_configured(curwin, &is_fixed); curbuf->b_signcols_max = -1; + lastp = &curbuf->b_signlist; - FOR_ALL_SIGNS_IN_BUF(curbuf, sign) { - new_lnum = sign->lnum; - if (sign->lnum >= line1 && sign->lnum <= line2) { - if (amount != MAXLNUM) { + for (sign = curbuf->b_signlist; sign != NULL; sign = next) { + next = sign->se_next; + new_lnum = sign->se_lnum; + if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { + if (amount == MAXLNUM && (!is_fixed || signcol >= 2)) { + *lastp = next; + if (next) { + next->se_prev = last; + } + xfree(sign); + continue; + } else { new_lnum += amount; } - } else if (sign->lnum > line2) { + } else if (sign->se_lnum > line2) { new_lnum += amount_after; } // If the new sign line number is past the last line in the buffer, // then don't adjust the line number. Otherwise, it will always be past // the last line and will not be visible. - if (sign->lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { - sign->lnum = new_lnum; + if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { + sign->se_lnum = new_lnum; } + + last = sign; + lastp = &sign->se_next; } } @@ -973,8 +1047,8 @@ int sign_place( sp->sn_typenr, has_text_or_icon); } else { - // ":sign place {id} file={fname}": change sign type - lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr); + // ":sign place {id} file={fname}": change sign type and/or priority + lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr, prio); } if (lnum > 0) { redraw_buf_line_later(buf, lnum); @@ -1488,7 +1562,7 @@ void sign_getlist(const char_u *name, list_T *retlist) list_T *get_buffer_signs(buf_T *buf) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - signlist_T *sign; + sign_entry_T *sign; dict_T *d; list_T *const l = tv_list_alloc(kListLenMayKnow); @@ -1509,7 +1583,7 @@ static void sign_get_placed_in_buf( { dict_T *d; list_T *l; - signlist_T *sign; + sign_entry_T *sign; d = tv_dict_alloc(); tv_list_append_dict(retlist, d); @@ -1524,9 +1598,9 @@ static void sign_get_placed_in_buf( continue; } if ((lnum == 0 && sign_id == 0) - || (sign_id == 0 && lnum == sign->lnum) - || (lnum == 0 && sign_id == sign->id) - || (lnum == sign->lnum && sign_id == sign->id)) { + || (sign_id == 0 && lnum == sign->se_lnum) + || (lnum == 0 && sign_id == sign->se_id) + || (lnum == sign->se_lnum && sign_id == sign->se_id)) { tv_list_append_dict(l, sign_get_info(sign)); } } @@ -1613,50 +1687,6 @@ static void sign_undefine(sign_T *sp, sign_T *sp_prev) xfree(sp); } -/// Gets highlighting attribute for sign "typenr" corresponding to "type". -int sign_get_attr(int typenr, SignType type) -{ - sign_T *sp; - int sign_hl = 0; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - switch (type) { - case SIGN_TEXT: - sign_hl = sp->sn_text_hl; - break; - case SIGN_LINEHL: - sign_hl = sp->sn_line_hl; - break; - case SIGN_NUMHL: - sign_hl = sp->sn_num_hl; - break; - default: - abort(); - } - if (sign_hl > 0) { - return syn_id2attr(sign_hl); - } - break; - } - } - return 0; -} - -/// Get text mark for sign "typenr". -/// Returns NULL if there isn't one. -char_u * sign_get_text(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp->sn_text; - } - } - return NULL; -} - /// Undefine/free all signs. void free_signs(void) { @@ -1860,3 +1890,267 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) } } +/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on +/// failure. +int sign_define_from_dict(const char *name_arg, dict_T *dict) +{ + char *name = NULL; + char *icon = NULL; + char *linehl = NULL; + char *text = NULL; + char *texthl = NULL; + char *numhl = NULL; + int retval = -1; + + if (name_arg == NULL) { + if (dict == NULL) { + return -1; + } + name = tv_dict_get_string(dict, "name", true); + } else { + name = xstrdup(name_arg); + } + if (name == NULL || name[0] == NUL) { + goto cleanup; + } + if (dict != NULL) { + icon = tv_dict_get_string(dict, "icon" , true); + linehl = tv_dict_get_string(dict, "linehl", true); + text = tv_dict_get_string(dict, "text" , true); + texthl = tv_dict_get_string(dict, "texthl", true); + numhl = tv_dict_get_string(dict, "numhl" , true); + } + + if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, + (char_u *)text, (char_u *)texthl, (char_u *)numhl) + == OK) { + retval = 0; + } + +cleanup: + xfree(name); + xfree(icon); + xfree(linehl); + xfree(text); + xfree(texthl); + xfree(numhl); + + return retval; +} + +/// Define multiple signs using attributes from list 'l' and store the return +/// values in 'retlist'. +void sign_define_multiple(list_T *l, list_T *retlist) +{ + int retval; + + TV_LIST_ITER_CONST(l, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + EMSG(_(e_dictreq)); + } + tv_list_append_number(retlist, retval); + }); +} + +/// Place a new sign using the values specified in dict 'dict'. Returns the sign +/// identifier if successfully placed, otherwise returns 0. +int sign_place_from_dict( + typval_T *id_tv, + typval_T *group_tv, + typval_T *name_tv, + typval_T *buf_tv, + dict_T *dict) +{ + int sign_id = 0; + char_u *group = NULL; + char_u *sign_name = NULL; + buf_T *buf = NULL; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + bool notanum = false; + int ret_sign_id = -1; + + // sign identifier + if (id_tv == NULL) { + di = tv_dict_find(dict, "id", -1); + if (di != NULL) { + id_tv = &di->di_tv; + } + } + if (id_tv == NULL) { + sign_id = 0; + } else { + sign_id = (int)tv_get_number_chk(id_tv, ¬anum); + if (notanum) { + return -1; + } + if (sign_id < 0) { + EMSG(_(e_invarg)); + return -1; + } + } + + // sign group + if (group_tv == NULL) { + di = tv_dict_find(dict, "group", -1); + if (di != NULL) { + group_tv = &di->di_tv; + } + } + if (group_tv == NULL) { + group = NULL; // global group + } else { + group = (char_u *)tv_get_string_chk(group_tv); + if (group == NULL) { + goto cleanup; + } + if (group[0] == '\0') { // global sign group + group = NULL; + } else { + group = vim_strsave(group); + if (group == NULL) { + return -1; + } + } + } + + // sign name + if (name_tv == NULL) { + di = tv_dict_find(dict, "name", -1); + if (di != NULL) { + name_tv = &di->di_tv; + } + } + if (name_tv == NULL) { + goto cleanup; + } + sign_name = (char_u *)tv_get_string_chk(name_tv); + if (sign_name == NULL) { + goto cleanup; + } + + // buffer to place the sign + if (buf_tv == NULL) { + di = tv_dict_find(dict, "buffer", -1); + if (di != NULL) { + buf_tv = &di->di_tv; + } + } + if (buf_tv == NULL) { + goto cleanup; + } + buf = get_buf_arg(buf_tv); + if (buf == NULL) { + goto cleanup; + } + + // line number of the sign + di = tv_dict_find(dict, "lnum", -1); + if (di != NULL) { + lnum = tv_get_lnum(&di->di_tv); + if (lnum <= 0) { + EMSG(_(e_invarg)); + goto cleanup; + } + } + + // sign priority + di = tv_dict_find(dict, "priority", -1); + if (di != NULL) { + prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + } + + if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) { + ret_sign_id = sign_id; + } + +cleanup: + xfree(group); + + return ret_sign_id; +} + +/// Undefine multiple signs +void sign_undefine_multiple(list_T *l, list_T *retlist) +{ + char_u *name; + int retval; + + TV_LIST_ITER_CONST(l, li, { + retval = -1; + name = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); + if (name != NULL && (sign_undefine_by_name(name) == OK)) { + retval = 0; + } + tv_list_append_number(retlist, retval); + }); +} + +/// Unplace the sign with attributes specified in 'dict'. Returns 0 on success +/// and -1 on failure. +int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) +{ + dictitem_T *di; + int sign_id = 0; + buf_T *buf = NULL; + char_u *group = NULL; + int retval = -1; + + // sign group + if (group_tv != NULL) { + group = (char_u *)tv_get_string(group_tv); + } else { + group = (char_u *)tv_dict_get_string(dict, "group", false); + } + if (group != NULL) { + if (group[0] == '\0') { // global sign group + group = NULL; + } else { + group = vim_strsave(group); + if (group == NULL) { + return -1; + } + } + } + + if (dict != NULL) { + if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { + buf = get_buf_arg(&di->di_tv); + if (buf == NULL) { + goto cleanup; + } + } + if (tv_dict_find(dict, "id", -1) != NULL) { + sign_id = (int)tv_dict_get_number(dict, "id"); + if (sign_id <= 0) { + EMSG(_(e_invarg)); + goto cleanup; + } + } + } + + if (buf == NULL) { + // Delete the sign in all the buffers + retval = 0; + FOR_ALL_BUFFERS(buf2) { + if (sign_unplace(sign_id, group, buf2, 0) != OK) { + retval = -1; + } + } + } else if (sign_unplace(sign_id, group, buf, 0) == OK) { + retval = 0; + } + +cleanup: + xfree(group); + + return retval; +} + diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index c898dba890..721b2db25b 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -10,39 +10,47 @@ // Sign group typedef struct signgroup_S { - uint16_t refcount; // number of signs in this group - int next_sign_id; // next sign id for this group - char_u sg_name[1]; // sign group name + uint16_t sg_refcount; // number of signs in this group + int sg_next_sign_id; // next sign id for this group + char_u sg_name[1]; // sign group name } signgroup_T; // Macros to get the sign group structure from the group name #define SGN_KEY_OFF offsetof(signgroup_T, sg_name) #define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF)) -typedef struct signlist signlist_T; - -struct signlist -{ - int id; // unique identifier for each placed sign - linenr_T lnum; // line number which has this sign - int typenr; // typenr of sign - bool has_text_or_icon; // has text or icon - signgroup_T *group; // sign group - int priority; // priority for highlighting - signlist_T *next; // next signlist entry - signlist_T *prev; // previous entry -- for easy reordering +typedef struct sign_entry sign_entry_T; + +struct sign_entry { + int se_id; // unique identifier for each placed sign + int se_typenr; // typenr of sign + int se_priority; // priority for highlighting + bool se_has_text_or_icon; // has text or icon + linenr_T se_lnum; // line number which has this sign + signgroup_T *se_group; // sign group + sign_entry_T *se_next; // next entry in a list of signs + sign_entry_T *se_prev; // previous entry -- for easy reordering }; +/// Sign attributes. Used by the screen refresh routines. +typedef struct sign_attrs_S { + int sat_typenr; + char_u *sat_text; + int sat_texthl; + int sat_linehl; + int sat_numhl; +} sign_attrs_T; + +#define SIGN_SHOW_MAX 9 + // Default sign priority for highlighting #define SIGN_DEF_PRIO 10 -// type argument for buf_getsigntype() and sign_get_attr() +// type argument for sign_get_attr() typedef enum { - SIGN_ANY, SIGN_LINEHL, - SIGN_ICON, - SIGN_TEXT, SIGN_NUMHL, + SIGN_TEXT, } SignType; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 6425c9fed5..f6dc3a04a7 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -112,6 +112,7 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/undo.h" +#include "nvim/ui.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -2889,8 +2890,14 @@ void spell_suggest(int count) msg_col = 0; // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + + if (ui_has(kUIMessages)) { + ui_call_msg_clear(); + } + + if (mouse_used) { selected -= lines_left; + } lines_left = Rows; // avoid more prompt // don't delay for 'smd' in normal_cmd() msg_scroll = msg_scroll_save; @@ -3028,7 +3035,7 @@ void ex_spellrepall(exarg_T *eap) sub_nlines = 0; curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 + if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 || u_save_cursor() == FAIL) { break; } @@ -6624,7 +6631,7 @@ void ex_spelldump(exarg_T *eap) if (no_spell_checking(curwin)) { return; } - get_option_value((char_u *)"spl", &dummy, &spl, OPT_LOCAL); + get_option_value("spl", &dummy, &spl, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); 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/state.c b/src/nvim/state.c index b195c1d96b..a3c74789d1 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -75,6 +75,34 @@ getkey: } } +/// process events on main_loop, but interrupt if input is available +/// +/// This should be used to handle K_EVENT in states accepting input +/// otherwise bursts of events can block break checking indefinitely. +void state_handle_k_event(void) +{ + while (true) { + Event event = multiqueue_get(main_loop.events); + if (event.handler) { + event.handler(event.argv); + } + + if (multiqueue_empty(main_loop.events)) { + // don't breakcheck before return, caller should return to main-loop + // and handle input already. + return; + } + + // TODO(bfredl): as an further micro-optimization, we could check whether + // event.handler already checked input. + os_breakcheck(); + if (input_available() || got_int) { + return; + } + } +} + + /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f99eca7953..77a751e5ad 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -59,7 +59,9 @@ struct hl_group { bool sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID + int sg_deflink; ///< default link; restored in highlight_clear() int sg_set; ///< combination of flags in \ref SG_SET + sctx_T sg_deflink_sctx; ///< script where the default link was set sctx_T sg_script_ctx; ///< script in which the group was last set // for terminal UIs int sg_cterm; ///< "cterm=" highlighting attr @@ -3419,7 +3421,7 @@ static void syn_cmd_on(exarg_T *eap, int syncing) */ static void syn_cmd_enable(exarg_T *eap, int syncing) { - set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable"); + set_internal_string_var("syntax_cmd", (char_u *)"enable"); syn_cmd_onoff(eap, "syntax"); do_unlet(S_LEN("g:syntax_cmd"), true); } @@ -3432,7 +3434,7 @@ static void syn_cmd_reset(exarg_T *eap, int syncing) { eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { - set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset"); + set_internal_string_var("syntax_cmd", (char_u *)"reset"); do_cmdline_cmd("runtime! syntax/syncolor.vim"); do_unlet(S_LEN("g:syntax_cmd"), true); } @@ -5304,13 +5306,17 @@ get_id_list( xfree(name); break; } - if (name[1] == 'A') - id = SYNID_ALLBUT; - else if (name[1] == 'T') - id = SYNID_TOP; - else - id = SYNID_CONTAINED; - id += current_syn_inc_tag; + if (name[1] == 'A') { + id = SYNID_ALLBUT + current_syn_inc_tag; + } else if (name[1] == 'T') { + if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) { + id = curwin->w_s->b_syn_topgrp; + } else { + id = SYNID_TOP + current_syn_inc_tag; + } + } else { + id = SYNID_CONTAINED + current_syn_inc_tag; + } } else if (name[1] == '@') { if (skip) { id = -1; @@ -5614,14 +5620,14 @@ void ex_ownsyntax(exarg_T *eap) // Move value of b:current_syntax to w:current_syntax. new_value = get_var_value("b:current_syntax"); if (new_value != NULL) { - set_internal_string_var((char_u *)"w:current_syntax", new_value); + set_internal_string_var("w:current_syntax", new_value); } // Restore value of b:current_syntax. if (old_value == NULL) { do_unlet(S_LEN("b:current_syntax"), true); } else { - set_internal_string_var((char_u *)"b:current_syntax", old_value); + set_internal_string_var("b:current_syntax", old_value); xfree(old_value); } } @@ -6044,6 +6050,9 @@ static const char *highlight_init_both[] = { "default link Whitespace NonText", "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", + "default link FloatBorder VertSplit", + "default FloatShadow blend=80 guibg=Black", + "default FloatShadowThrough blend=100 guibg=Black", "RedrawDebugNormal cterm=reverse gui=reverse", "RedrawDebugClear ctermbg=Yellow guibg=Yellow", "RedrawDebugComposed ctermbg=Green guibg=Green", @@ -6601,6 +6610,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) const char *to_end; int from_id; int to_id; + struct hl_group *hlgroup = NULL; from_end = (const char *)skiptowhite((const char_u *)from_start); to_start = (const char *)skipwhite((const char_u *)from_end); @@ -6627,7 +6637,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) (int)(to_end - to_start)); } - if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { + if (from_id > 0) { + hlgroup = &HL_TABLE()[from_id - 1]; + if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { + hlgroup->sg_deflink = to_id; + hlgroup->sg_deflink_sctx = current_sctx; + hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + } + + if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { // Don't allow a link when there already is some highlighting // for the group, unless '!' is used if (to_id > 0 && !forceit && !init @@ -6635,17 +6654,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); } - } else if (HL_TABLE()[from_id - 1].sg_link != to_id - || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid - != current_sctx.sc_sid - || HL_TABLE()[from_id - 1].sg_cleared) { + } else if (hlgroup->sg_link != to_id + || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid + || hlgroup->sg_cleared) { if (!init) { - HL_TABLE()[from_id - 1].sg_set |= SG_LINK; + hlgroup->sg_set |= SG_LINK; } - HL_TABLE()[from_id - 1].sg_link = to_id; - HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx; - HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum; - HL_TABLE()[from_id - 1].sg_cleared = false; + hlgroup->sg_link = to_id; + hlgroup->sg_script_ctx = current_sctx; + hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); // Only call highlight changed() once after multiple changes @@ -7076,13 +7094,14 @@ void restore_cterm_colors(void) */ static int hl_has_settings(int idx, int check_link) { - return HL_TABLE()[idx].sg_attr != 0 - || HL_TABLE()[idx].sg_cterm_fg != 0 - || HL_TABLE()[idx].sg_cterm_bg != 0 - || HL_TABLE()[idx].sg_rgb_fg_name != NULL - || HL_TABLE()[idx].sg_rgb_bg_name != NULL - || HL_TABLE()[idx].sg_rgb_sp_name != NULL - || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)); + return HL_TABLE()[idx].sg_cleared == 0 + && (HL_TABLE()[idx].sg_attr != 0 + || HL_TABLE()[idx].sg_cterm_fg != 0 + || HL_TABLE()[idx].sg_cterm_bg != 0 + || HL_TABLE()[idx].sg_rgb_fg_name != NULL + || HL_TABLE()[idx].sg_rgb_bg_name != NULL + || HL_TABLE()[idx].sg_rgb_sp_name != NULL + || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); } /* @@ -7105,12 +7124,11 @@ static void highlight_clear(int idx) XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); HL_TABLE()[idx].sg_blend = -1; - // Clear the script ID only when there is no link, since that is not - // cleared. - if (HL_TABLE()[idx].sg_link == 0) { - HL_TABLE()[idx].sg_script_ctx.sc_sid = 0; - HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0; - } + // Restore default link and context if they exist. Otherwise clears. + HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; + // Since we set the default link, set the location to where the default + // link was set. + HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; } diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 9fbad74f64..38f848f178 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -27,6 +27,8 @@ #define HL_CONCEAL 0x20000 /* can be concealed */ #define HL_CONCEALENDS 0x40000 /* can be concealed */ +#define SYN_GROUP_STATIC(s) syn_check_group((char_u *)S_LEN(s)) + typedef struct { char *name; RgbValue color; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index c6b1a0d04c..a6310344e9 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -625,7 +625,7 @@ do_tag( } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1007L, true); } } @@ -908,7 +908,7 @@ add_llist_tags( if (len > 128) { len = 128; } - xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len + 1); tag_name[len] = NUL; // Save the tag file name @@ -975,7 +975,8 @@ add_llist_tags( if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + snprintf((char *)cmd + len, CMDBUFFSIZE + 1 - len, + "%.*s", cmd_len, cmd_start); len += cmd_len; if (cmd[len - 1] == '$') { @@ -1141,7 +1142,7 @@ static int find_tagfunc_tags( int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[3]; + char_u flagString[4]; dict_T *d; taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; @@ -1170,9 +1171,10 @@ static int find_tagfunc_tags( args[3].v_type = VAR_UNKNOWN; vim_snprintf((char *)flagString, sizeof(flagString), - "%s%s", + "%s%s%s", g_tag_at_cursor ? "c": "", - flags & TAG_INS_COMP ? "i": ""); + flags & TAG_INS_COMP ? "i": "", + flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); @@ -1461,7 +1463,7 @@ find_tags( p_ic = ignorecase_opt(pat, true, true); break; default: - assert(false); + abort(); } help_save = curbuf->b_help; @@ -2809,7 +2811,7 @@ static int jumpto_tag( // start search before first line curwin->w_cursor.lnum = 0; } - if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, + if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { retval = OK; } else { @@ -2819,8 +2821,8 @@ static int jumpto_tag( /* * try again, ignore case now */ - p_ic = TRUE; - if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, + p_ic = true; + if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { // Failed to find pattern, take a guess: "^func (" found = 2; @@ -2828,11 +2830,12 @@ static int jumpto_tag( cc = *tagp.tagname_end; *tagp.tagname_end = NUL; snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, + search_options, NULL)) { found = 0; } } @@ -2850,7 +2853,7 @@ static int jumpto_tag( MSG(_("E435: Couldn't find tag, just guessing!")); if (!msg_scrolled && msg_silent == 0) { ui_flush(); - os_delay(1000L, true); + os_delay(1010L, true); } } retval = OK; @@ -3002,7 +3005,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, */ static int find_extra(char_u **pp) { - char_u *str = *pp; + char_u *str = *pp; + char_u first_char = **pp; // Repeat for addresses separated with ';' for (;; ) { @@ -3010,7 +3014,7 @@ static int find_extra(char_u **pp) str = skipdigits(str); } else if (*str == '/' || *str == '?') { str = skip_regexp(str + 1, *str, false, NULL); - if (*str != **pp) { + if (*str != first_char) { str = NULL; } else { str++; @@ -3028,6 +3032,7 @@ static int find_extra(char_u **pp) break; } str++; // skip ';' + first_char = *str; } if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { @@ -3152,7 +3157,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) bool is_static; ret = find_tags(pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, (int)MAXCOL, buf_fname); + TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; ++i) { int parse_result = parse_match(matches[i], &tp); @@ -3404,6 +3409,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { + EMSG(_(e_listreq)); return FAIL; } l = di->di_tv.vval.v_list; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 39e2ca6171..afad20f557 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -169,19 +169,20 @@ void terminal_teardown(void) multiqueue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); + invalidated_terminals = NULL; } // public API {{{ -Terminal *terminal_open(TerminalOptions opts) +Terminal *terminal_open(buf_T *buf, TerminalOptions opts) { // Create a new terminal instance and configure it Terminal *rv = xcalloc(1, sizeof(Terminal)); rv->opts = opts; rv->cursor.visible = true; // Associate the terminal instance with the new buffer - rv->buf_handle = curbuf->handle; - curbuf->terminal = rv; + rv->buf_handle = buf->handle; + buf->terminal = rv; // Create VTerm rv->vt = vterm_new(opts.height, opts.width); vterm_set_utf8(rv->vt, 1); @@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts) // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; rv->invalid_end = opts.height; - refresh_screen(rv, curbuf); + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + refresh_screen(rv, buf); set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666 // Default settings for terminal buffers - curbuf->b_p_ma = false; // 'nomodifiable' - curbuf->b_p_ul = -1; // 'undolevels' - curbuf->b_p_scbk = // 'scrollback' (initialize local from global) + buf->b_p_ma = false; // 'nomodifiable' + buf->b_p_ul = -1; // 'undolevels' + buf->b_p_scbk = // 'scrollback' (initialize local from global) (p_scbk < 0) ? 10000 : MAX(1, p_scbk); - curbuf->b_p_tw = 0; // 'textwidth' + buf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); - buf_set_term_title(curbuf, (char *)curbuf->b_ffname); + if (buf->b_ffname != NULL) { + buf_set_term_title(buf, (char *)buf->b_ffname); + } RESET_BINDING(curwin); // Reset cursor in current window. curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; // Apply TermOpen autocmds _before_ configuring the scrollback buffer. - apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf); // Local 'scrollback' _after_ autocmds. - curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk; + buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk; + + aucmd_restbuf(&aco); // Configure the scrollback buffer. - rv->sb_size = (size_t)curbuf->b_p_scbk; + rv->sb_size = (size_t)buf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); // Configure the color palette. Try to get the color from: @@ -457,7 +466,7 @@ static int terminal_execute(VimState *state, int key) case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; - multiqueue_process_events(main_loop.events); + state_handle_k_event(); s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; @@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term) } if (!term->refcount) { - if (pmap_has(ptr_t)(invalidated_terminals, term)) { + // might be destroyed after terminal_teardown is invoked + if (invalidated_terminals + && pmap_has(ptr_t)(invalidated_terminals, term)) { // flush any pending changes to the buffer block_autocmds(); refresh_terminal(term); @@ -535,8 +546,47 @@ 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) { + if (y_size == 0) { + return; + } + 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 +594,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) @@ -1269,6 +1338,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(bfredl): remove this condition int changed = 0; int added = 0; int height; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e52fd888bd..4641408069 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -31,20 +31,11 @@ endif SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. -NEW_TESTS_ALOT := test_alot_utf8 test_alot +NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) -NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim) # Ignored tests. -# test_alot_latin: Nvim does not allow setting encoding. -# test_autochdir: ported to Lua, but kept for easier merging. -# test_eval_func: used as include in old-style test (test_eval.in). -# test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. NEW_TESTS_IGNORE := \ - test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \ - test_autochdir \ - test_eval_func \ - test_listlbr \ test_largefile \ NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim)))) diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 24d3959f83..7b06e53dd5 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -108,3 +108,30 @@ func CheckNotMSWindows() throw 'Skipped: does not work on MS-Windows' endif endfunc + +" Command to check for satisfying any of the conditions. +" e.g. CheckAnyOf Feature:bsd Feature:sun Linux +command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) +func CheckAnyOf(...) + let excp = [] + for arg in a:000 + try + exe 'Check' .. substitute(arg, ':', ' ', '') + return + catch /^Skipped:/ + let excp += [substitute(v:exception, '^Skipped:\s*', '', '')] + endtry + endfor + throw 'Skipped: ' .. join(excp, '; ') +endfunc + +" Command to check for satisfying all of the conditions. +" e.g. CheckAllOf Unix Gui Option:ballooneval +command -nargs=+ CheckAllOf call CheckAllOf(<f-args>) +func CheckAllOf(...) + for arg in a:000 + exe 'Check' .. substitute(arg, ':', ' ', '') + endfor +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 275edece1e..49993c03aa 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -13,6 +13,9 @@ " For csh: " setenv TEST_FILTER Test_channel " +" While working on a test you can make $TEST_NO_RETRY non-empty to not retry: +" export TEST_NO_RETRY=yes +" " To ignore failure for tests that are known to fail in a certain environment, " set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for " sh/bash: @@ -373,9 +376,6 @@ let s:flaky_tests = [ \ 'Test_with_partial_callback()', \ ] -" Pattern indicating a common flaky test failure. -let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump' - " Locate Test_ functions and execute them. redir @q silent function /^Test_ @@ -410,14 +410,19 @@ for s:test in sort(s:tests) let total_errors = [] let run_nr = 1 + " A test can set g:test_is_flaky to retry running the test. + let g:test_is_flaky = 0 + call RunTheTest(s:test) " Repeat a flaky test. Give up when: + " - $TEST_NO_RETRY is not empty " - it fails again with the same message " - it fails five times (with a different message) if len(v:errors) > 0 + \ && $TEST_NO_RETRY == '' \ && (index(s:flaky_tests, s:test) >= 0 - \ || v:errors[0] =~ s:flaky_errors_re) + \ || g:test_is_flaky) while 1 call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim new file mode 100644 index 0000000000..9913b1dfaf --- /dev/null +++ b/src/nvim/testdir/script_util.vim @@ -0,0 +1,69 @@ +" Functions shared by the tests for Vim Script + +" Commands to track the execution path of a script +com! XpathINIT let g:Xpath = '' +com! -nargs=1 -bar Xpath let g:Xpath ..= <args> +com! XloopINIT let g:Xloop = 1 +com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop +com! XloopNEXT let g:Xloop += 1 + +" MakeScript() - Make a script file from a function. {{{2 +" +" Create a script that consists of the body of the function a:funcname. +" Replace any ":return" by a ":finish", any argument variable by a global +" variable, and every ":call" by a ":source" for the next following argument +" in the variable argument list. This function is useful if similar tests are +" to be made for a ":return" from a function call or a ":finish" in a script +" file. +func MakeScript(funcname, ...) + let script = tempname() + execute "redir! >" . script + execute "function" a:funcname + redir END + execute "edit" script + " Delete the "function" and the "endfunction" lines. Do not include the + " word "function" in the pattern since it might be translated if LANG is + " set. When MakeScript() is being debugged, this deletes also the debugging + " output of its line 3 and 4. + exec '1,/.*' . a:funcname . '(.*)/d' + /^\d*\s*endfunction\>/,$d + %s/^\d*//e + %s/return/finish/e + %s/\<a:\(\h\w*\)/g:\1/ge + normal gg0 + let cnt = 0 + while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 + let cnt = cnt + 1 + s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ + endwhile + g/^\s*$/d + write + bwipeout + return script +endfunc + +" ExecAsScript - Source a temporary script made from a function. {{{2 +" +" Make a temporary script file from the function a:funcname, ":source" it, and +" delete it afterwards. However, if an exception is thrown the file may remain, +" the caller should call DeleteTheScript() afterwards. +let s:script_name = '' +function! ExecAsScript(funcname) + " Make a script from the function passed as argument. + let s:script_name = MakeScript(a:funcname) + + " Source and delete the script. + exec "source" s:script_name + call delete(s:script_name) + let s:script_name = '' +endfunction + +function! DeleteTheScript() + if s:script_name + call delete(s:script_name) + let s:script_name = '' + endif +endfunc + +com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) + diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 4f056abdc0..e50602ccad 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -33,11 +33,11 @@ source test_move.vim source test_partial.vim source test_popup.vim source test_put.vim -source test_recover.vim +source test_rename.vim source test_scroll_opt.vim +source test_shift.vim source test_sort.vim source test_sha256.vim -source test_statusline.vim source test_suspend.vim source test_syn_attr.vim source test_tabline.vim diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim index ebb3bde4ce..23a404cac1 100644 --- a/src/nvim/testdir/test_alot_latin.vim +++ b/src/nvim/testdir/test_alot_latin.vim @@ -4,7 +4,4 @@ " These tests use latin1 'encoding'. Setting 'encoding' is in the individual " files, so that they can be run by themselves. -" Nvim does not allow setting 'encoding', so skip this test group. -finish - source test_regexp_latin.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index be0bd01413..70f14320a6 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_listlbr_utf8.vim source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 92fedf9bfb..a1ef8325ec 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -80,6 +80,24 @@ func Test_argadd() call assert_equal(0, len(argv())) endfunc +func Test_argadd_empty_curbuf() + new + let curbuf = bufnr('%') + call writefile(['test', 'Xargadd'], 'Xargadd') + " must not re-use the current buffer. + argadd Xargadd + call assert_equal(curbuf, bufnr('%')) + call assert_equal('', bufname('%')) + call assert_equal(1, line('$')) + rew + call assert_notequal(curbuf, bufnr('%')) + call assert_equal('Xargadd', bufname('%')) + call assert_equal(2, line('$')) + + %argd + bwipe! +endfunc + func Init_abc() args a b c next diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index b4f7478807..1d114221dc 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -52,6 +52,37 @@ func Test_assert_fails_in_try_block() endtry endfunc +func Test_assert_inrange() + call assert_equal(0, assert_inrange(7, 7, 7)) + call assert_equal(0, assert_inrange(5, 7, 5)) + call assert_equal(0, assert_inrange(5, 7, 6)) + call assert_equal(0, assert_inrange(5, 7, 7)) + call assert_equal(1, assert_inrange(5, 7, 4)) + call assert_match("Expected range 5 - 7, but got 4", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8)) + call assert_match("Expected range 5 - 7, but got 8", v:errors[0]) + call remove(v:errors, 0) + + call assert_fails('call assert_inrange(1, 1)', 'E119:') + + if has('float') + call assert_equal(0, assert_inrange(7.0, 7, 7)) + call assert_equal(0, assert_inrange(7, 7.0, 7)) + call assert_equal(0, assert_inrange(7, 7, 7.0)) + call assert_equal(0, assert_inrange(5, 7, 5.0)) + call assert_equal(0, assert_inrange(5, 7, 6.0)) + call assert_equal(0, assert_inrange(5, 7, 7.0)) + + call assert_equal(1, assert_inrange(5, 7, 4.0)) + call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8.0)) + call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0]) + call remove(v:errors, 0) + endif +endfunc + " Must be last. func Test_zz_quit_detected() " Verify that if a test function ends Vim the test script detects this. diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 67c537b407..d071f4b325 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -1,10 +1,10 @@ " Test 'autochdir' behavior -if !exists("+autochdir") - throw 'Skipped: autochdir feature missing' -endif +source check.vim +CheckOption autochdir func Test_set_filename() + CheckFunction test_autochdir let cwd = getcwd() call test_autochdir() set acd @@ -17,3 +17,5 @@ func Test_set_filename() exe 'cd ' . cwd call delete('samples/Xtest') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index c571e37ac3..5611560b1b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -76,7 +76,7 @@ if has('timers') endfunc func Test_OptionSet_modeline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) au! OptionSet augroup set_tabstop @@ -276,28 +276,28 @@ func Test_augroup_warning() augroup TheWarning au VimEnter * echo 'entering' augroup END - call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0) + call assert_match("TheWarning.*VimEnter", execute('au VimEnter')) redir => res augroup! TheWarning redir END - call assert_true(match(res, "W19:") >= 0) - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("W19:", res) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) " check "Another" does not take the pace of the deleted entry augroup Another augroup END - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) augroup! Another " no warning for postpone aucmd delete augroup StartOK au VimEnter * call RemoveGroup() augroup END - call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0) + call assert_match("StartOK.*VimEnter", execute('au VimEnter')) redir => res doautocmd VimEnter redir END - call assert_true(match(res, "W19:") < 0) + call assert_notmatch("W19:", res) au! VimEnter endfunc @@ -325,7 +325,7 @@ func Test_augroup_deleted() au VimEnter * echo augroup end augroup! x - call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + call assert_match("-Deleted-.*VimEnter", execute('au VimEnter')) au! VimEnter endfunc @@ -507,7 +507,7 @@ func s:AutoCommandOptionSet(match) endfunc func Test_OptionSet() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !has("eval") || !exists("+autochdir") return endif @@ -648,7 +648,7 @@ func Test_OptionSet() endfunc func Test_OptionSet_diffmode() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 18: Changing an option when entering diff mode new @@ -682,7 +682,7 @@ func Test_OptionSet_diffmode() endfunc func Test_OptionSet_diffmode_close() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 19: Try to close the current window when entering diff mode " should not segfault @@ -1279,32 +1279,15 @@ 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 +" See test/functional/viml/completion_spec.lua' func Test_ChangedP() - " Nvim does not support test_override(). - throw 'skipped: see test/functional/viml/completion_spec.lua' + CheckFunction test_override new call setline(1, ['foo', 'bar', 'foobar']) call test_override("char_avail", 1) @@ -1367,7 +1350,7 @@ func SetLineOne() endfunc func Test_TextChangedI_with_setline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override('char_avail', 1) autocmd TextChangedI <buffer> call SetLineOne() @@ -1383,9 +1366,11 @@ func Test_TextChangedI_with_setline() endfunc func Test_Changed_FirstTime() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + " Starting a terminal to run Vim is always considered flaky. + let g:test_is_flaky = 1 + " Prepare file for TextChanged event. call writefile([''], 'Xchanged.txt') let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3}) @@ -1939,21 +1924,40 @@ func Test_autocmd_window() %bw! edit one.txt tabnew two.txt + vnew three.txt + tabnew four.txt + tabprevious let g:blist = [] - augroup aucmd_win_test + augroup aucmd_win_test1 au! au BufEnter * call add(g:blist, [expand('<afile>'), \ win_gettype(bufwinnr(expand('<afile>')))]) augroup END doautoall BufEnter - call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist) + call assert_equal([ + \ ['one.txt', 'autocmd'], + \ ['two.txt', ''], + \ ['four.txt', 'autocmd'], + \ ['three.txt', ''], + \ ], g:blist) - augroup aucmd_win_test + augroup aucmd_win_test1 au! augroup END - augroup! aucmd_win_test + augroup! aucmd_win_test1 %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 index d680b442db..11459991ea 100644 --- a/src/nvim/testdir/test_backspace_opt.vim +++ b/src/nvim/testdir/test_backspace_opt.vim @@ -1,15 +1,5 @@ " 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) @@ -41,10 +31,10 @@ func Test_backspace_option() 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')) + call assert_fails('set backspace=ABC', 'E474:') + call assert_fails('set backspace+=def', 'E474:') " NOTE: Vim doesn't check following error... - "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) + "call assert_fails('set backspace-=ghi', 'E474:') " Check backwards compatibility with version 5.4 and earlier set backspace=0 @@ -55,8 +45,8 @@ func Test_backspace_option() 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')) + call assert_fails('set backspace=4', 'E474:') + call assert_fails('set backspace=10', 'E474:') " Cleared when 'compatible' is set " set compatible diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index a4c1f62a43..ff5029b889 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -12,56 +12,88 @@ source view_util.vim let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" -function s:screen_lines(lnum, width) abort +func s:screen_lines(lnum, width) abort return ScreenLines([a:lnum, a:lnum + 2], a:width) -endfunction +endfunc -function! s:compare_lines(expect, actual) +func! s:compare_lines(expect, actual) call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) -endfunction +endfunc -function s:test_windows(...) +func s:test_windows(...) call NewWindow(10, 20) setl ts=4 sw=4 sts=4 breakindent put =s:input exe get(a:000, 0, '') -endfunction +endfunc -function s:close_windows(...) +func s:close_windows(...) call CloseWindow() exe get(a:000, 0, '') -endfunction +endfunc -function Test_breakindent01() +func Test_breakindent01() " simple breakindent test call s:test_windows('setl briopt=min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrst", -\ " GHIJ", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrst", + \ " GHIJ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc -function Test_breakindent02() +func Test_breakindent01_vartabs() + " like 01 but with vartabs feature + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrst", + \ " GHIJ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc + +func Test_breakindent02() " simple breakindent test with showbreak set call s:test_windows('setl briopt=min:0 sbr=>>') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " >>qr", -\ " >>EF", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " >>qr", + \ " >>EF", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent02_vartabs() + if !has("vartabs") + return + endif + " simple breakindent test with showbreak set + call s:test_windows('setl briopt=min:0 sbr=>> vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " >>qr", + \ " >>EF", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent03() +func Test_breakindent03() " simple breakindent test with showbreak set and briopt including sbr call s:test_windows('setl briopt=sbr,min:0 sbr=++') - let lines=s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'),8) let expect=[ \ " abcd", \ "++ qrst", @@ -70,77 +102,177 @@ function Test_breakindent03() call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent03_vartabs() + " simple breakindent test with showbreak set and briopt including sbr + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ "++ qrst", + \ "++ GHIJ", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent04() +func Test_breakindent04() " breakindent set with min width 18 call s:test_windows('setl sbr= briopt=min:18') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrstuv", -\ " IJKLMN", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstuv", + \ " IJKLMN", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc -function Test_breakindent05() +func Test_breakindent04_vartabs() + " breakindent set with min width 18 + if !has("vartabs") + return + endif + call s:test_windows('setl sbr= briopt=min:18 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstuv", + \ " IJKLMN", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc + +func Test_breakindent05() " breakindent set and shift by 2 call s:test_windows('setl briopt=shift:2,min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qr", -\ " EF", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qr", + \ " EF", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent05_vartabs() + " breakindent set and shift by 2 + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=shift:2,min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qr", + \ " EF", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc -function Test_breakindent06() +func Test_breakindent06() " breakindent set and shift by -1 call s:test_windows('setl briopt=shift:-1,min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrstu", -\ " HIJKL", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstu", + \ " HIJKL", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc -function Test_breakindent07() +func Test_breakindent06_vartabs() + " breakindent set and shift by -1 + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=shift:-1,min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstu", + \ " HIJKL", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc + +func Test_breakindent07() " breakindent set and shift by 1, Number set sbr=? and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ "? m", -\ "? x", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "? m", + \ "? x", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc + +func Test_breakindent07_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number set sbr=? and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "? m", + \ "? x", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= cpo-=n vts&') +endfunc -function Test_breakindent07a() +func Test_breakindent07a() " breakindent set and shift by 1, Number set sbr=? and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ " ? m", -\ " ? x", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ " ? m", + \ " ? x", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent07a_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number set sbr=? and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ " ? m", + \ " ? x", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent08() +func Test_breakindent08() " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4') " make sure, cache is invalidated! @@ -148,43 +280,96 @@ function Test_breakindent08() redraw! set ts=4 redraw! - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ "# opq", -\ "# BCD", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ "# opq", + \ "# BCD", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc -function Test_breakindent08a() +func Test_breakindent08_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4') + " make sure, cache is invalidated! + set ts=8 + redraw! + set ts=4 + redraw! + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ "# opq", + \ "# BCD", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= cpo-=n vts&') +endfunc + +func Test_breakindent08a() " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ " # opq", -\ " # BCD", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " # opq", + \ " # BCD", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent08a_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " # opq", + \ " # BCD", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent09() +func Test_breakindent09() " breakindent set and shift by 1, Number and list set sbr=# call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ " #op", -\ " #AB", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " #op", + \ " #AB", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent09_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# + call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " #op", + \ " #AB", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent10() +func Test_breakindent10() " breakindent set, Number set sbr=~ call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0') " make sure, cache is invalidated! @@ -192,41 +377,91 @@ function Test_breakindent10() redraw! set ts=4 redraw! - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ "~ mn", -\ "~ yz", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "~ mn", + \ "~ yz", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc + +func Test_breakindent10_vartabs() + if !has("vartabs") + return + endif + " breakindent set, Number set sbr=~ + call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4') + " make sure, cache is invalidated! + set ts=8 + redraw! + set ts=4 + redraw! + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "~ mn", + \ "~ yz", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= cpo-=n vts&') +endfunc -function Test_breakindent11() +func Test_breakindent11() " test strdisplaywidth() call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4') let text=getline(2) let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times call assert_equal(width, strdisplaywidth(text)) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent11_vartabs() + if !has("vartabs") + return + endif + " test strdisplaywidth() + call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4') + let text = getline(2) + let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times + call assert_equal(width, strdisplaywidth(text)) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent12() +func Test_breakindent12() " test breakindent with long indent let s:input="\t\t\t\t\t{" call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-') - let lines=s:screen_lines(2,16) - let expect=[ -\ " 2 >--->--->--->", -\ " ---{ ", -\ "~ ", -\ ] + let lines = s:screen_lines(2,16) + let expect = [ + \ " 2 >--->--->--->", + \ " ---{ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set nuw=4 listchars=') -endfunction +endfunc -function Test_breakindent13() - let s:input="" +func Test_breakindent12_vartabs() + if !has("vartabs") + return + endif + " test breakindent with long indent + let s:input = "\t\t\t\t\t{" + call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4') + let lines = s:screen_lines(2,16) + let expect = [ + \ " 2 >--->--->--->", + \ " ---{ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set nuw=4 listchars= vts&') +endfunc + +func Test_breakindent13() + let s:input = "" call s:test_windows('setl breakindent briopt=min:10 ts=8') vert resize 20 call setline(1, [" a\tb\tc\td\te", " z y x w v"]) @@ -237,65 +472,149 @@ function Test_breakindent13() call assert_equal('d', @a) call assert_equal('w', @b) call s:close_windows() -endfunction +endfunc + +func Test_breakindent13_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8') + vert resize 20 + call setline(1, [" a\tb\tc\td\te", " z y x w v"]) + 1 + norm! fbgj"ayl + 2 + norm! fygj"byl + call assert_equal('d', @a) + call assert_equal('w', @b) + call s:close_windows('set vts&') +endfunc -function Test_breakindent14() - let s:input="" +func Test_breakindent14() + let s:input = "" call s:test_windows('setl breakindent briopt= ts=8') vert resize 30 norm! 3a1234567890 norm! a abcde exec "norm! 0\<C-V>tex" - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ "e ", -\ "~ ", -\ "~ ", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ "e ", + \ "~ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc -function Test_breakindent15() - let s:input="" +func Test_breakindent14_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt= ts=8 vts=8') + vert resize 30 + norm! 3a1234567890 + norm! a abcde + exec "norm! 0\<C-V>tex" + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ "e ", + \ "~ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc + +func Test_breakindent15() + let s:input = "" call s:test_windows('setl breakindent briopt= ts=8 sw=8') vert resize 30 norm! 4a1234567890 exe "normal! >>\<C-V>3f0x" - let lines=s:screen_lines(line('.'),20) - let expect=[ -\ " 1234567890 ", -\ "~ ", -\ "~ ", -\ ] + let lines = s:screen_lines(line('.'),20) + let expect = [ + \ " 1234567890 ", + \ "~ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent15_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8') + vert resize 30 + norm! 4a1234567890 + exe "normal! >>\<C-V>3f0x" + let lines = s:screen_lines(line('.'),20) + let expect = [ + \ " 1234567890 ", + \ "~ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc -function Test_breakindent16() +func Test_breakindent16() " Check that overlong lines are indented correctly. - let s:input="" + let s:input = "" call s:test_windows('setl breakindent briopt=min:0 ts=4') call setline(1, "\t".repeat("1234567890", 10)) resize 6 norm! 1gg$ redraw! - let lines=s:screen_lines(1,10) - let expect=[ -\ " 789012", -\ " 345678", -\ " 901234", -\ ] + let lines = s:screen_lines(1,10) + let expect = [ + \ " 789012", + \ " 345678", + \ " 901234", + \ ] call s:compare_lines(expect, lines) - let lines=s:screen_lines(4,10) - let expect=[ -\ " 567890", -\ " 123456", -\ " 7890 ", -\ ] + let lines = s:screen_lines(4,10) + let expect = [ + \ " 567890", + \ " 123456", + \ " 7890 ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent16_vartabs() + if !has("vartabs") + return + endif + " Check that overlong lines are indented correctly. + let s:input = "" + call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4') + call setline(1, "\t".repeat("1234567890", 10)) + resize 6 + norm! 1gg$ + redraw! + let lines = s:screen_lines(1,10) + let expect = [ + \ " 789012", + \ " 345678", + \ " 901234", + \ ] + call s:compare_lines(expect, lines) + let lines = s:screen_lines(4,10) + let expect = [ + \ " 567890", + \ " 123456", + \ " 7890 ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc func Test_breakindent17_vartabs() if !has("vartabs") @@ -353,14 +672,19 @@ func Test_breakindent19_sbr_nextpage() " Scroll down one screen line setl scrolloff=5 norm! 5gj - redraw! let lines = s:screen_lines(1, 20) let expect = [ - \ "> aaaaaaaaaaaaaaaaaa", + \ "aaaaaaaaaaaaaaaaaaaa", \ "> aaaaaaaaaaaaaaaaaa", \ "> aaaaaaaaaaaaaaaaaa", \ ] call s:compare_lines(expect, lines) + redraw! + " moving the cursor doesn't change the text offset + norm! l + redraw! + let lines = s:screen_lines(1, 20) + call s:compare_lines(expect, lines) setl breakindent briopt=min:18 sbr=> norm! 5gj diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim new file mode 100644 index 0000000000..40111fdf06 --- /dev/null +++ b/src/nvim/testdir/test_buffer.vim @@ -0,0 +1,33 @@ +" Tests for Vim buffer + +func Test_buffer_error() + new foo1 + new foo2 + + call assert_fails('buffer foo', 'E93:') + call assert_fails('buffer bar', 'E94:') + call assert_fails('buffer 0', 'E939:') + + %bwipe +endfunc + +func Test_badd_options() + new SomeNewBuffer + setlocal numberwidth=3 + wincmd p + badd +1 SomeNewBuffer + new SomeNewBuffer + call assert_equal(3, &numberwidth) + close + close + bwipe! SomeNewBuffer +endfunc + +func Test_balt() + new SomeNewBuffer + balt +3 OtherBuffer + e # + call assert_equal('OtherBuffer', bufname()) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index 076f03fdd8..e038bce08e 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -112,6 +112,17 @@ func Test_deletebufline() call assert_equal(0, deletebufline(b, 1)) call assert_equal(['b', 'c'], getbufline(b, 1, 2)) exe "bwipe! " . b + + edit XbufOne + let one = bufnr() + call setline(1, ['a', 'b', 'c']) + setlocal nomodifiable + split XbufTwo + let two = bufnr() + call assert_fails('call deletebufline(one, 1)', 'E21:') + call assert_equal(two, bufnr()) + bwipe! XbufTwo + bwipe! XbufOne endfunc func Test_appendbufline_redraw() diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 39f865144a..489b2477e6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -442,6 +442,7 @@ func Test_getcompletion() set tags& call assert_fails('call getcompletion("", "burp")', 'E475:') + call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc func Test_shellcmd_completion() @@ -568,6 +569,21 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func s:ScriptLocalFunction() + echo 'yes' +endfunc + +func Test_cmdline_complete_user_func() + call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func Test_cmdline_complete_user', @:) + call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:) + + " g: prefix also works + call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"echo g:Test_cmdline_complete_user_func', @:) +endfunc + func Test_cmdline_complete_user_names() if has('unix') && executable('whoami') let whoami = systemlist('whoami')[0] diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index 7262789ab4..55b230373f 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -158,7 +158,9 @@ endfunc func Test_command_count_4() %argd let bufnr = bufnr('$') - arga aa bb cc dd ee ff + next aa bb cc dd ee ff + call assert_equal(bufnr, bufnr('%')) + 3argu let args = [] .,$-argdo call add(args, expand('%')) diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 9101f8cfa0..c3de7d0050 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -37,23 +37,33 @@ 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', @:) + call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"compiler! pbx perl php pylint pyunit', @:) + call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) endfunc func Test_compiler_error() diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim new file mode 100644 index 0000000000..1306dbe5cf --- /dev/null +++ b/src/nvim/testdir/test_conceal.vim @@ -0,0 +1,282 @@ +" Tests for 'conceal'. + +source check.vim +CheckFeature conceal + +source screendump.vim +" CheckScreendump + +func Test_conceal_two_windows() + CheckScreendump + let code =<< trim [CODE] + let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"] + call setline(1, lines) + syntax match test /|hidden|/ conceal + set conceallevel=2 + set concealcursor= + exe "normal /here\r" + new + call setline(1, lines) + call setline(4, "Second window") + syntax match test /|hidden|/ conceal + set conceallevel=2 + set concealcursor=nc + exe "normal /here\r" + [CODE] + + call writefile(code, 'XTest_conceal') + " Check that cursor line is concealed + let buf = RunVimInTerminal('-S XTest_conceal', {}) + call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {}) + + " Check that with concealed text vertical cursor movement is correct. + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_02', {}) + + " Check that with cursor line is not concealed + call term_sendkeys(buf, "j") + call term_sendkeys(buf, ":set concealcursor=\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_03', {}) + + " Check that with cursor line is not concealed when moving cursor down + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_04', {}) + + " Check that with cursor line is not concealed when switching windows + call term_sendkeys(buf, "\<C-W>\<C-W>") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_05', {}) + + " Check that with cursor line is only concealed in Normal mode + call term_sendkeys(buf, ":set concealcursor=n\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_06v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Insert mode + call term_sendkeys(buf, ":set concealcursor=i\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_07v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Command mode + call term_sendkeys(buf, ":set concealcursor=c\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_08v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check that with cursor line is only concealed in Visual mode + call term_sendkeys(buf, ":set concealcursor=v\r") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09n', {}) + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09i', {}) + call term_sendkeys(buf, "\<Esc>/e") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09c', {}) + call term_sendkeys(buf, "\<Esc>v") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_09v', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check moving the cursor while in insert mode. + call term_sendkeys(buf, ":set concealcursor=\r") + call term_sendkeys(buf, "a") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_10', {}) + call term_sendkeys(buf, "\<Down>") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_11', {}) + call term_sendkeys(buf, "\<Esc>") + + " Check the "o" command + call VerifyScreenDump(buf, 'Test_conceal_two_windows_12', {}) + call term_sendkeys(buf, "o") + call VerifyScreenDump(buf, 'Test_conceal_two_windows_13', {}) + call term_sendkeys(buf, "\<Esc>") + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal') +endfunc + +func Test_conceal_with_cursorline() + CheckScreendump + " Opens a help window, where 'conceal' is set, switches to the other window + " where 'cursorline' needs to be updated when the cursor moves. + let code =<< trim [CODE] + set cursorline + normal othis is a test + new + call setline(1, ["one", "two", "three", "four", "five"]) + set ft=help + normal M + [CODE] + + call writefile(code, 'XTest_conceal_cul') + let buf = RunVimInTerminal('-S XTest_conceal_cul', {}) + call VerifyScreenDump(buf, 'Test_conceal_cul_01', {}) + + call term_sendkeys(buf, ":wincmd w\r") + call VerifyScreenDump(buf, 'Test_conceal_cul_02', {}) + + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_conceal_cul_03', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal_cul') +endfunc + +func Test_conceal_resize_term() + CheckScreendump + let code =<< trim [CODE] + call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed') + setl cocu=n cole=3 + syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends + normal fb + [CODE] + call writefile(code, 'XTest_conceal_resize') + let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_conceal_resize_01', {}) + + call win_execute(buf->win_findbuf()[0], 'wincmd +') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_conceal_resize_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_conceal_resize') +endfunc + +" Tests for correct display (cursor column position) with +conceal and +" tabulators. Need to run this test in a separate Vim instance. Otherwise the +" screen is not updated (lazy redraw) and the cursor position is wrong. +func Test_conceal_cursor_pos() + let code =<< trim [CODE] + :let l = ['start:', '.concealed. text', "|concealed|\ttext"] + :let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", ''] + :let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"] + :call append(0, l) + :call cursor(1, 1) + :" Conceal settings. + :set conceallevel=2 + :set concealcursor=nc + :syntax match test /|/ conceal + :" Save current cursor position. Only works in <expr> mode, can't be used + :" with :normal because it moves the cursor to the command line. Thanks + :" to ZyX <zyx.vim@gmail.com> for the idea to use an <expr> mapping. + :let curpos = [] + :nnoremap <expr> GG ":let curpos += ['".screenrow().":".screencol()."']\n" + :normal ztj + GGk + :" We should end up in the same column when running these commands on the + :" two lines. + :normal ft + GGk + :normal $ + GGk + :normal 0j + GGk + :normal ft + GGk + :normal $ + GGk + :normal 0j0j + GGk + :" Same for next test block. + :normal ft + GGk + :normal $ + GGk + :normal 0j + GGk + :normal ft + GGk + :normal $ + GGk + :normal 0j0j + GGk + :" And check W with multiple tabs and conceals in a line. + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :normal 0j + GGk + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :set lbr + :normal $ + GGk + :set list listchars=tab:>- + :normal 0 + GGk + :normal W + GGk + :normal W + GGk + :normal W + GGk + :normal $ + GGk + :call writefile(curpos, 'Xconceal_curpos.out') + :q! + + [CODE] + call writefile(code, 'XTest_conceal_curpos') + + if RunVim([], [], '-s XTest_conceal_curpos') + call assert_equal([ + \ '2:1', '2:17', '2:20', '3:1', '3:17', '3:20', '5:8', '5:25', + \ '5:28', '6:8', '6:25', '6:28', '8:1', '8:9', '8:17', '8:25', + \ '8:27', '9:1', '9:9', '9:17', '9:25', '9:26', '9:26', '9:1', + \ '9:9', '9:17', '9:25', '9:26'], readfile('Xconceal_curpos.out')) + endif + + call delete('Xconceal_curpos.out') + call delete('XTest_conceal_curpos') +endfunc + +func Test_conceal_eol() + new! + setlocal concealcursor=n conceallevel=1 + call setline(1, ["x", ""]) + call matchaddpos('Conceal', [[2, 1, 1]], 2, -1, {'conceal': 1}) + redraw! + + call assert_notequal(screenchar(1, 1), screenchar(2, 2)) + call assert_equal(screenattr(1, 1), screenattr(1, 2)) + call assert_equal(screenattr(1, 2), screenattr(2, 2)) + call assert_equal(screenattr(2, 1), screenattr(2, 2)) + + set list + redraw! + + call assert_equal(screenattr(1, 1), screenattr(2, 2)) + call assert_notequal(screenattr(1, 1), screenattr(1, 2)) + call assert_notequal(screenattr(1, 2), screenattr(2, 1)) + + set nolist +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 2e190911b2..53b7da517e 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -92,6 +92,11 @@ func Test_screenpos() \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) close bwipe! + + call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + nmenu WinBar.TEST : + call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + nunmenu WinBar.TEST endfunc func Test_screenpos_number() diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 59d51b855b..d1464e9d3b 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -2,6 +2,31 @@ source shared.vim source screendump.vim +source check.vim + +func CheckCWD() + " Check that the longer lines don't wrap due to the length of the script name + " in cwd + let script_len = len( getcwd() .. '/Xtest1.vim' ) + let longest_line = len( 'Breakpoint in "" line 1' ) + if script_len > ( 75 - longest_line ) + throw 'Skipped: Your CWD has too many characters' + endif +endfunc +command! -nargs=0 -bar CheckCWD call CheckCWD() + +func CheckDbgOutput(buf, lines, options = {}) + " Verify the expected output + let lnum = 20 - len(a:lines) + for l in a:lines + if get(a:options, 'match', 'equal') ==# 'pattern' + call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200) + else + call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200) + endif + let lnum += 1 + endfor +endfunc " Run a Vim debugger command " If the expected output argument is supplied, then check for it. @@ -10,20 +35,17 @@ func RunDbgCmd(buf, cmd, ...) call term_wait(a:buf) if a:0 != 0 - " Verify the expected output - let lnum = 20 - len(a:1) - for l in a:1 - call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}) - let lnum += 1 - endfor + let options = #{match: 'equal'} + if a:0 > 1 + call extend(options, a:2) + endif + call CheckDbgOutput(a:buf, a:1, options) endif endfunc " Debugger tests func Test_Debugger() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Create a Vim script with some functions let lines =<< trim END @@ -317,6 +339,785 @@ func Test_Debugger() call delete('Xtest.vim') endfunc +func Test_Backtrace_Through_Source() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug call GlobalFunction()', + \ ['cmd: call GlobalFunction()']) + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'backtrace', ['>backtrace', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()']) + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + call RunDbgCmd(buf, 'backtrace', ['>backtrace', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[1]', + \ '->0 SourceAnotherFile', + \ 'line 1: source Xtest2.vim']) + + " Step into the 'source' command. Note that we print the full trace all the + " way though the source command. + call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()']) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) + + " step until we have another meaninfgul trace + call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 9: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 5 function GlobalFunction[1]', + \ ' 4 CallAFunction[1]', + \ ' 3 SourceAnotherFile[1]', + \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]', + \ ' 1 function File2Function[1]', + \ '->0 DoAThing', + \ 'line 1: echo "DoAThing"']) + + " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 10: End of sourced file']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 2: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: call DoAThing()']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_Autocmd() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + + au User TestGlobalFunction :call GlobalFunction() | echo "Done" + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug doautocmd User TestGlobalFunction', + \ ['cmd: doautocmd User TestGlobalFunction']) + call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"']) + + " At this point the ontly thing in the stack is the autocommand + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 User Autocommands for "TestGlobalFunction"', + \ 'cmd: call GlobalFunction() | echo "Done"']) + + " And now we're back into the call stack + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 User Autocommands for "TestGlobalFunction"', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()']) + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[1]', + \ '->0 SourceAnotherFile', + \ 'line 1: source Xtest2.vim']) + + " Step into the 'source' command. Note that we print the full trace all the + " way though the source command. + call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()']) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) + + " step until we have another meaninfgul trace + call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 9: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 6 User Autocommands for "TestGlobalFunction"', + \ ' 5 function GlobalFunction[1]', + \ ' 4 CallAFunction[1]', + \ ' 3 SourceAnotherFile[1]', + \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]', + \ ' 1 function File2Function[1]', + \ '->0 DoAThing', + \ 'line 1: echo "DoAThing"']) + + " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 10: End of sourced file']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 2: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 User Autocommands for "TestGlobalFunction"', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: call DoAThing()']) + + + " Now unwind so that we get back to the original autocommand (and the second + " cmd echo "Done") + call RunDbgCmd(buf, 'finish', ['line 1: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: End of function']) + + call RunDbgCmd(buf, 'finish', ['line 2: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 User Autocommands for "TestGlobalFunction"', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: End of function']) + + call RunDbgCmd(buf, 'finish', ['line 1: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 User Autocommands for "TestGlobalFunction"', + \ '->0 function GlobalFunction', + \ 'line 1: End of function']) + + call RunDbgCmd(buf, 'step', ['cmd: echo "Done"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 User Autocommands for "TestGlobalFunction"', + \ 'cmd: echo "Done"']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_CmdLine() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + + au User TestGlobalFunction :call GlobalFunction() | echo "Done" + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal( + \ '-S Xtest1.vim -c "debug call GlobalFunction()"', + \ {'wait_for_ruler': 0}) + + " Need to wait for the vim-in-terminal to be ready + call CheckDbgOutput(buf, ['command line', + \ 'cmd: call GlobalFunction()']) + + " At this point the ontly thing in the stack is the cmdline + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 command line', + \ 'cmd: call GlobalFunction()']) + + " And now we're back into the call stack + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 command line', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_DefFunction() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + vim9script + import File2Function from './Xtest2.vim' + + def SourceAnotherFile() + source Xtest2.vim + enddef + + def CallAFunction() + SourceAnotherFile() + File2Function() + enddef + + def g:GlobalFunction() + CallAFunction() + enddef + + defcompile + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + vim9script + + def DoAThing(): number + var a = 100 * 2 + a += 3 + return a + enddef + + export def File2Function() + DoAThing() + enddef + + defcompile + File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug call GlobalFunction()', + \ ['cmd: call GlobalFunction()']) + + " FIXME: Vim9 lines are not debugged! + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + " But they do appear in the backtrace + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 function GlobalFunction[1]', + \ '\V 1 <SNR>\.\*_CallAFunction[1]', + \ '\V->0 <SNR>\.\*_SourceAnotherFile', + \ '\Vline 1: source Xtest2.vim'], + \ #{match: 'pattern'}) + + + call RunDbgCmd(buf, 'step', ['line 1: vim9script']) + call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number']) + call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: def File2Function()']) + call RunDbgCmd(buf, 'step', ['line 13: defcompile']) + call RunDbgCmd(buf, 'step', ['line 14: File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 function GlobalFunction[1]', + \ '\V 2 <SNR>\.\*_CallAFunction[1]', + \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]', + \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim', + \ '\Vline 14: File2Function()'], + \ #{match: 'pattern'}) + + " Don't step into compiled functions... + call RunDbgCmd(buf, 'step', ['line 15: End of sourced file']) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 function GlobalFunction[1]', + \ '\V 2 <SNR>\.\*_CallAFunction[1]', + \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]', + \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim', + \ '\Vline 15: End of sourced file'], + \ #{match: 'pattern'}) + + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_debug_backtrace_level() + CheckRunVimInTerminal + CheckCWD + let lines =<< trim END + let s:file1_var = 'file1' + let g:global_var = 'global' + + func s:File1Func( arg ) + let s:file1_var .= a:arg + let local_var = s:file1_var .. ' test1' + let g:global_var .= local_var + source Xtest2.vim + endfunc + + call s:File1Func( 'arg1' ) + END + call writefile(lines, 'Xtest1.vim') + + let lines =<< trim END + let s:file2_var = 'file2' + + func s:File2Func( arg ) + let s:file2_var .= a:arg + let local_var = s:file2_var .. ' test2' + let g:global_var .= local_var + endfunc + + call s:File2Func( 'arg2' ) + END + call writefile(lines, 'Xtest2.vim') + + let file1 = getcwd() .. '/Xtest1.vim' + let file2 = getcwd() .. '/Xtest2.vim' + + " set a breakpoint and source file1.vim + let buf = RunVimInTerminal( + \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim', + \ #{ wait_for_ruler: 0 } ) + + call CheckDbgOutput(buf, [ + \ 'Breakpoint in "' .. file1 .. '" line 1', + \ 'Entering Debug mode. Type "cont" to continue.', + \ 'command line..script ' .. file1, + \ 'line 1: let s:file1_var = ''file1''' + \ ]) + + " step throught the initial declarations + call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] ) + call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + call RunDbgCmd(buf, 'echo global_var', [ 'global' ] ) + + " step in to the first function + call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] ) + call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] ) + call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + call RunDbgCmd(buf, + \'echo global_var', + \[ 'E121: Undefined variable: global_var' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + + " backtrace up + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V 1 script ' .. file1 .. '[11]', + \ '\V->0 function <SNR>\.\*_File1Func', + \ '\Vline 1: let s:file1_var .= a:arg', + \ ], + \ #{ match: 'pattern' } ) + call RunDbgCmd(buf, 'up', [ '>up' ] ) + + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V->1 script ' .. file1 .. '[11]', + \ '\V 0 function <SNR>\.\*_File1Func', + \ '\Vline 1: let s:file1_var .= a:arg', + \ ], + \ #{ match: 'pattern' } ) + + " Expression evaluation in the script frame (not the function frame) + " FIXME: Unexpected in this scope (a: should not be visibnle) + call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + " FIXME: Unexpected in this scope (global should be found) + call RunDbgCmd(buf, + \'echo global_var', + \[ 'E121: Undefined variable: global_var' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + + + " step while backtraced jumps to the latest frame + call RunDbgCmd(buf, 'step', [ + \ 'line 2: let local_var = s:file1_var .. '' test1''' ] ) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V 1 script ' .. file1 .. '[11]', + \ '\V->0 function <SNR>\.\*_File1Func', + \ '\Vline 2: let local_var = s:file1_var .. '' test1''', + \ ], + \ #{ match: 'pattern' } ) + + call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] ) + call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] ) + call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] ) + + call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] ) + call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] ) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V 2 script ' .. file1 .. '[11]', + \ '\V 1 function <SNR>\.\*_File1Func[4]', + \ '\V->0 script ' .. file2, + \ '\Vline 1: let s:file2_var = ''file2''', + \ ], + \ #{ match: 'pattern' } ) + + " Expression evaluation in the script frame file2 (not the function frame) + call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] ) + call RunDbgCmd(buf, + \ 'echo s:file1_var', + \ [ 'E121: Undefined variable: s:file1_var' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] ) + call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + call RunDbgCmd(buf, + \ 'echo s:file2_var', + \ [ 'E121: Undefined variable: s:file2_var' ] ) + + call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] ) + call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] ) + + " Up the stack to the other script context + call RunDbgCmd(buf, 'up') + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V 2 script ' .. file1 .. '[11]', + \ '\V->1 function <SNR>\.\*_File1Func[4]', + \ '\V 0 script ' .. file2, + \ '\Vline 3: func s:File2Func( arg )', + \ ], + \ #{ match: 'pattern' } ) + " FIXME: Unexpected. Should see the a: and l: dicts from File1Func + call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] ) + call RunDbgCmd(buf, + \ 'echo l:local_var', + \ [ 'E121: Undefined variable: l:local_var' ] ) + + call RunDbgCmd(buf, 'up') + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V->2 script ' .. file1 .. '[11]', + \ '\V 1 function <SNR>\.\*_File1Func[4]', + \ '\V 0 script ' .. file2, + \ '\Vline 3: func s:File2Func( arg )', + \ ], + \ #{ match: 'pattern' } ) + + " FIXME: Unexpected (wrong script vars are used) + call RunDbgCmd(buf, + \ 'echo s:file1_var', + \ [ 'E121: Undefined variable: s:file1_var' ] ) + call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] ) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + " Test for setting a breakpoint on a :endif where the :if condition is false " and then quit the script. This should generate an interrupt. func Test_breakpt_endif_intr() diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index f09a64c329..21c1f98283 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -242,6 +242,63 @@ func Test_diffput_two() bwipe! b endfunc +" :diffput and :diffget completes names of buffers which +" are in diff mode and which are different then current buffer. +" No completion when the current window is not in diff mode. +func Test_diffget_diffput_completion() + e Xdiff1 | diffthis + botright new Xdiff2 + botright new Xdiff3 | split | diffthis + botright new Xdiff4 | diffthis + + wincmd t + call assert_equal('Xdiff1', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff3 Xdiff4', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff3 Xdiff4', @:) + call assert_equal(['Xdiff3', 'Xdiff4'], getcompletion('', 'diff_buffer')) + + " Xdiff2 is not in diff mode, so no completion for :diffput, :diffget + wincmd j + call assert_equal('Xdiff2', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput ', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget ', @:) + call assert_equal([], getcompletion('', 'diff_buffer')) + + " Xdiff3 is split in 2 windows, only the top one is in diff mode. + " So completion of :diffput :diffget only happens in the top window. + wincmd j + call assert_equal('Xdiff3', bufname('%')) + call assert_equal(1, &diff) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff1 Xdiff4', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff1 Xdiff4', @:) + call assert_equal(['Xdiff1', 'Xdiff4'], getcompletion('', 'diff_buffer')) + + wincmd j + call assert_equal('Xdiff3', bufname('%')) + call assert_equal(0, &diff) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput ', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget ', @:) + call assert_equal([], getcompletion('', 'diff_buffer')) + + wincmd j + call assert_equal('Xdiff4', bufname('%')) + call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput Xdiff1 Xdiff3', @:) + call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget Xdiff1 Xdiff3', @:) + call assert_equal(['Xdiff1', 'Xdiff3'], getcompletion('', 'diff_buffer')) + + %bwipe +endfunc + func Test_dp_do_buffer() e! one let bn1=bufnr('%') @@ -964,6 +1021,21 @@ func Test_diff_closeoff() enew! endfunc +func Test_diff_followwrap() + new + set diffopt+=followwrap + set wrap + diffthis + call assert_equal(1, &wrap) + diffoff + set nowrap + diffthis + call assert_equal(0, &wrap) + diffoff + set diffopt& + bwipe! +endfunc + func Test_diff_rnu() CheckScreendump @@ -992,6 +1064,18 @@ func Test_diff_rnu() call delete('Xtest_diff_rnu') endfunc +func Test_diff_multilineconceal() + new + diffthis + + new + call matchadd('Conceal', 'a\nb', 9, -1, {'conceal': 'Y'}) + set cole=2 cocu=n + call setline(1, ["a", "b"]) + diffthis + redraw +endfunc + func Test_diff_and_scroll() " this was causing an ml_get error set ls=2 diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index b6d9687560..d23748a3e3 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -211,6 +211,8 @@ func Test_digraphs() call Put_Dig("00") call Put_Dig("el") call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) + call assert_fails('digraph xy z', 'E39:') + call assert_fails('digraph x', 'E474:') bw! endfunc diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 061364fb73..73b57f302e 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 function('winnr')", '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_excmd.vim b/src/nvim/testdir/test_excmd.vim index 20508b12d3..98a3e60368 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -131,3 +131,11 @@ func Test_confirm_cmd_cancel() \ term_getline(buf, 20))}, 1000) call StopVimInTerminal(buf) endfunc + +" Test for the :winsize command +func Test_winsize_cmd() + call assert_fails('winsize 1', 'E465:') + call assert_fails('winsize 1 x', 'E465:') + call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)') + " Actually changing the window size would be flaky. +endfunc diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 99a401d4a4..bd3e9eb4d4 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -81,3 +81,32 @@ func Test_exiting() endif call delete('Xtestout') endfunc + +" Test for getting the Vim exit code from v:exiting +func Test_exit_code() + call assert_equal(v:null, v:exiting) + + let before =<< trim [CODE] + au QuitPre * call writefile(['qp = ' .. v:exiting], 'Xtestout', 'a') + au ExitPre * call writefile(['ep = ' .. v:exiting], 'Xtestout', 'a') + au VimLeavePre * call writefile(['lp = ' .. v:exiting], 'Xtestout', 'a') + au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout', 'a') + [CODE] + + if RunVim(before, ['quit'], '') + call assert_equal(['qp = null', 'ep = null', 'lp = 0', 'l = 0'], readfile('Xtestout')) + endif + call delete('Xtestout') + + if RunVim(before, ['cquit'], '') + call assert_equal(['lp = 1', 'l = 1'], readfile('Xtestout')) + endif + call delete('Xtestout') + + if RunVim(before, ['cquit 4'], '') + call assert_equal(['lp = 4', 'l = 4'], readfile('Xtestout')) + endif + call delete('Xtestout') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 7b90ba56e0..0b41a1127a 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -399,7 +399,11 @@ function Test_printf_errors() call assert_fails('echo printf("%d", [])', 'E745:') call assert_fails('echo printf("%d", 1, 2)', 'E767:') call assert_fails('echo printf("%*d", 1)', 'E766:') - call assert_fails('echo printf("%d", 1.2)', 'E805:') + call assert_fails('echo printf("%s")', 'E766:') + if has('float') + call assert_fails('echo printf("%d", 1.2)', 'E805:') + call assert_fails('echo printf("%f")') + endif endfunc function Test_max_min_errors() @@ -497,3 +501,12 @@ func Test_empty_concatenate() call assert_equal('b', 'a'[4:0] . 'b') call assert_equal('b', 'b' . 'a'[4:0]) endfunc + +func Test_eval_after_if() + let s:val = '' + func SetVal(x) + let s:val ..= a:x + endfunc + if 0 | eval SetVal('a') | endif | call SetVal('b') + call assert_equal('b', s:val) +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index f9f0ade1f6..3cfc964f0a 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -79,6 +79,7 @@ let s:filename_checks = { \ 'bc': ['file.bc'], \ 'bdf': ['file.bdf'], \ 'bib': ['file.bib'], + \ 'beancount': ['file.beancount'], \ 'bindzone': ['named.root'], \ 'blank': ['file.bl'], \ 'bsdl': ['file.bsd', 'file.bsdl'], @@ -274,6 +275,8 @@ let s:filename_checks = { \ 'lss': ['file.lss'], \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], \ 'lynx': ['lynx.cfg'], + \ 'm3build': ['m3makefile', 'm3overrides'], + \ 'm3quake': ['file.quake', 'cm3.cfg'], \ 'm4': ['file.at'], \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'], \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'], @@ -357,6 +360,7 @@ let s:filename_checks = { \ 'po': ['file.po', 'file.pot'], \ 'pod': ['file.pod'], \ 'pod6': ['file.pod6'], + \ 'poke': ['file.pk'], \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], \ 'pov': ['file.pov'], \ 'povini': ['.povrayrc'], @@ -369,7 +373,10 @@ let s:filename_checks = { \ 'promela': ['file.pml'], \ 'proto': ['file.proto'], \ 'protocols': ['/etc/protocols'], + \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'], + \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], + \ 'psl': ['file.psl'], \ 'puppet': ['file.pp'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], @@ -413,7 +420,7 @@ let s:filename_checks = { \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf'], \ 'services': ['/etc/services'], \ 'setserial': ['/etc/serial.conf'], - \ 'sh': ['/etc/udev/cdsymlinks.conf'], + \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'simula': ['file.sim'], \ 'sinda': ['file.sin', 'file.s85'], @@ -427,6 +434,7 @@ let s:filename_checks = { \ 'slrnrc': ['.slrnrc'], \ 'slrnsc': ['file.score'], \ 'sm': ['sendmail.cf'], + \ 'svelte': ['file.svelte'], \ 'smarty': ['file.tpl'], \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], \ 'smith': ['file.smt', 'file.smith'], @@ -466,7 +474,7 @@ let s:filename_checks = { \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], - \ 'text': ['file.text', 'README'], + \ 'text': ['file.text', 'README', '/usr/share/doc/bash-completion/AUTHORS'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], @@ -518,7 +526,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1'], \ 'xmodmap': ['anyXmodmap'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm2': ['file.xpm2'], diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 2d058e8e32..fcdf888b96 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -823,31 +823,36 @@ func Test_fold_create_delete() endfunc func Test_fold_relative_move() - enew! + new set fdm=indent sw=2 wrap tw=80 - let content = [ ' foo', ' bar', ' baz', - \ repeat('x', &columns + 1), - \ ' foo', ' bar', ' baz' + let longtext = repeat('x', &columns + 1) + let content = [ ' foo', ' ' .. longtext, ' baz', + \ longtext, + \ ' foo', ' ' .. longtext, ' baz' \ ] call append(0, content) normal zM - call cursor(3, 1) - call assert_true(foldclosed(line('.'))) - normal gj - call assert_equal(2, winline()) + for lnum in range(1, 3) + call cursor(lnum, 1) + call assert_true(foldclosed(line('.'))) + normal gj + call assert_equal(2, winline()) + endfor 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()) + for lnum in range(5, 7) + call cursor(lnum, 1) + call assert_true(foldclosed(line('.'))) + normal gk + call assert_equal(3, winline()) + endfor call cursor(6, 1) call assert_true(foldclosed(line('.'))) diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 917a5e8eca..93f567b3a0 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,5 +1,7 @@ " Tests for various functions. + source shared.vim +source check.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -171,9 +173,8 @@ func Test_str2nr() endfunc func Test_strftime() - if !exists('*strftime') - return - endif + CheckFunction strftime + " Format of strftime() depends on system. We assume " that basic formats tested here are available and " identical on all systems which support strftime(). @@ -214,6 +215,33 @@ func Test_strftime() endif endfunc +func Test_strptime() + CheckFunction strptime + CheckNotMSWindows + + if exists('$TZ') + let tz = $TZ + endif + let $TZ = 'UTC' + + call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + " Force DST and check that it's considered + let $TZ = 'WINTER0SUMMER,J1,J365' + call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + call assert_fails('call strptime()', 'E119:') + call assert_fails('call strptime("xxx")', 'E119:') + call assert_equal(0, strptime("%Y", '')) + call assert_equal(0, strptime("%Y", "xxx")) + + if exists('tz') + let $TZ = tz + else + unlet $TZ + endif +endfunc + func Test_resolve_unix() if !has('unix') return @@ -833,6 +861,31 @@ func Test_byte2line_line2byte() bw! endfunc +" Test for charidx() +func Test_charidx() + let a = 'xáb́y' + call assert_equal(0, charidx(a, 0)) + call assert_equal(1, charidx(a, 3)) + call assert_equal(2, charidx(a, 4)) + call assert_equal(3, charidx(a, 7)) + call assert_equal(-1, charidx(a, 8)) + call assert_equal(-1, charidx('', 0)) + + " count composing characters + call assert_equal(0, charidx(a, 0, 1)) + call assert_equal(2, charidx(a, 2, 1)) + call assert_equal(3, charidx(a, 4, 1)) + call assert_equal(5, charidx(a, 7, 1)) + call assert_equal(-1, charidx(a, 8, 1)) + call assert_equal(-1, charidx('', 0, 1)) + + call assert_fails('let x = charidx([], 1)', 'E474:') + call assert_fails('let x = charidx("abc", [])', 'E474:') + call assert_fails('let x = charidx("abc", 1, [])', 'E474:') + call assert_fails('let x = charidx("abc", 1, -1)', 'E474:') + call assert_fails('let x = charidx("abc", 1, 2)', 'E474:') +endfunc + func Test_count() let l = ['a', 'a', 'A', 'b'] call assert_equal(2, count(l, 'a')) @@ -930,10 +983,20 @@ func Test_Executable() " get "cat" path and remove the leading / let catcmd = exepath('cat')[1:] new + " check that the relative path works in / lcd / call assert_equal(1, executable(catcmd)) - call assert_equal('/' .. catcmd, exepath(catcmd)) + " let result = catcmd->exepath() + let result = exepath(catcmd) + " when using chroot looking for sbin/cat can return bin/cat, that is OK + if catcmd =~ '\<sbin\>' && result =~ '\<bin\>' + call assert_equal('/' .. substitute(catcmd, '\<sbin\>', 'bin', ''), result) + else + call assert_equal('/' .. catcmd, result) + endif bwipe + else + throw 'Skipped: does not work on this platform' endif endfunc @@ -1008,10 +1071,10 @@ func Test_inputlist() endfunc func Test_balloon_show() - if has('balloon_eval') - " This won't do anything but must not crash either. - call balloon_show('hi!') - endif + CheckFeature balloon_eval + + " This won't do anything but must not crash either. + call balloon_show('hi!') endfunc func Test_shellescape() @@ -1385,4 +1448,12 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +func HasDefault(msg = 'msg') + return a:msg +endfunc + +func Test_default_arg_value() + call assert_equal('msg', HasDefault()) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 9acec51913..d09b25b0e7 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -1,9 +1,8 @@ " Test for gn command func Test_gn_command() - set belloff=all noautocmd new - " replace a single char by itsself quoted: + " replace a single char by itself quoted: call setline('.', 'abc x def x ghi x jkl') let @/ = 'x' exe "norm! cgn'x'\<esc>.." @@ -157,7 +156,6 @@ func Test_gn_command() sil! %d _ set wrapscan&vim - set belloff&vim endfunc func Test_gN_repeat() diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 4cc4d775d1..ce22de09ca 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -3,6 +3,7 @@ source view_util.vim source screendump.vim source check.vim +source script_util.vim func Test_highlight() " basic test if ":highlight" doesn't crash @@ -623,4 +624,103 @@ func Test_xxlast_highlight_RGB_color() hi clear endfunc +func Test_highlight_clear_restores_links() + let aaa_id = hlID('aaa') + call assert_equal(aaa_id, 0) + + " create default link aaa --> bbb + hi def link aaa bbb + let id_aaa = hlID('aaa') + let hl_aaa_bbb = HighlightArgs('aaa') + + " try to redefine default link aaa --> ccc; check aaa --> bbb + hi def link aaa ccc + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " clear aaa; check aaa --> bbb + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " link aaa --> ccc; clear aaa; check aaa --> bbb + hi link aaa ccc + let id_ccc = hlID('ccc') + call assert_equal(synIDtrans(id_aaa), id_ccc) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " forcibly set default link aaa --> ddd + hi! def link aaa ddd + let id_ddd = hlID('ddd') + let hl_aaa_ddd = HighlightArgs('aaa') + call assert_equal(synIDtrans(id_aaa), id_ddd) + + " link aaa --> eee; clear aaa; check aaa --> ddd + hi link aaa eee + let eee_id = hlID('eee') + call assert_equal(synIDtrans(id_aaa), eee_id) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd) +endfunc + +func Test_highlight_clear_restores_context() + func FuncContextDefault() + hi def link Context ContextDefault + endfun + + func FuncContextRelink() + " Dummy line + hi link Context ContextRelink + endfunc + + let scriptContextDefault = MakeScript("FuncContextDefault") + let scriptContextRelink = MakeScript("FuncContextRelink") + let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1' + let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2' + + exec "source" scriptContextDefault + let hlContextDefault = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextDefault) + + exec "source" scriptContextRelink + let hlContextRelink = execute("verbose hi Context") + call assert_match(patContextRelink, hlContextRelink) + + hi clear + let hlContextAfterClear = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextAfterClear) + + delfunc FuncContextDefault + delfunc FuncContextRelink + call delete(scriptContextDefault) + call delete(scriptContextRelink) +endfunc + +func Test_highlight_default_colorscheme_restores_links() + hi link TestLink Identifier + hi TestHi ctermbg=red + + let hlTestLinkPre = HighlightArgs('TestLink') + let hlTestHiPre = HighlightArgs('TestHi') + + " Test colorscheme + hi clear + if exists('syntax_on') + syntax reset + endif + let g:colors_name = 'test' + hi link TestLink ErrorMsg + hi TestHi ctermbg=green + + " Restore default highlighting + colorscheme default + " 'default' should work no matter if highlight group was cleared + hi def link TestLink Identifier + hi def TestHi ctermbg=red + let hlTestLinkPost = HighlightArgs('TestLink') + let hlTestHiPost = HighlightArgs('TestHi') + call assert_equal(hlTestLinkPre, hlTestLinkPost) + call assert_equal(hlTestHiPre, hlTestHiPost) + hi clear +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 9435931d41..3da3648fec 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -469,6 +469,34 @@ func Test_pum_with_folds_two_tabs() call delete('Xpumscript') endfunc +" Test for inserting the tag search pattern in insert mode +func Test_ins_compl_tag_sft() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}$/", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t/^int third() {}$/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + set showfulltag + exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>" + call assert_equal('int second() {}', getline(1)) + set noshowfulltag + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe! +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index dcc588120c..4cb609aaf0 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -110,6 +110,35 @@ func Test_listchars() \ '.....h>-$', \ 'iii<<<<><<$', '$'], l) + " Test lead and trail + normal ggdG + set listchars=eol:$ + set listchars+=lead:>,trail:<,space:x + set list + + call append(0, [ + \ ' ffff ', + \ ' gg', + \ 'h ', + \ ' ', + \ ' 0 0 ', + \ ]) + + let expected = [ + \ '>>>>ffff<<<<$', + \ '>>>>>>>>>>gg$', + \ 'h<<<<<<<<<<<$', + \ '<<<<<<<<<<<<$', + \ '>>>>0xx0<<<<$', + \ '$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + call assert_equal(expected, split(execute("%list"), "\n")) " test nbsp normal ggdG diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 8e2a987e74..affb141a26 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -506,6 +506,15 @@ func Test_dict_lock_extend() call assert_equal({'a': 99, 'b': 100}, d) endfunc +" Cannot use += with a locked dict +func Test_dict_lock_operator() + unlet! d + let d = {} + lockvar d + call assert_fails("let d += {'k' : 10}", 'E741:') + unlockvar d +endfunc + " No remove() of write-protected scope-level variable func! Tfunc(this_is_a_long_parameter_name) call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742') @@ -709,6 +718,23 @@ func Test_listdict_extend() call assert_fails("call extend([1, 2], 1)", 'E712:') call assert_fails("call extend([1, 2], {})", 'E712:') + + " Extend g: dictionary with an invalid variable name + call assert_fails("call extend(g:, {'-!' : 10})", 'E461:') + + " Extend a list with itself. + let l = [1, 5, 7] + call extend(l, l, 0) + call assert_equal([1, 5, 7, 1, 5, 7], l) + let l = [1, 5, 7] + call extend(l, l, 1) + call assert_equal([1, 1, 5, 7, 5, 7], l) + let l = [1, 5, 7] + call extend(l, l, 2) + call assert_equal([1, 5, 1, 5, 7, 7], l) + let l = [1, 5, 7] + call extend(l, l, 3) + call assert_equal([1, 5, 7, 1, 5, 7], l) endfunc func s:check_scope_dict(x, fixed) @@ -782,3 +808,40 @@ func Test_scope_dict() " Test for v: call s:check_scope_dict('v', v:true) endfunc + +" Test for a null list +func Test_null_list() + let l = v:_null_list + call assert_equal('', join(l)) + call assert_equal(0, len(l)) + call assert_equal(1, empty(l)) + call assert_fails('let s = join([1, 2], [])', 'E730:') + call assert_equal([], split(v:_null_string)) + call assert_equal([], l[:2]) + call assert_true([] == l) + call assert_equal('[]', string(l)) + " call assert_equal(0, sort(l)) + " call assert_equal(0, sort(l)) + " call assert_equal(0, uniq(l)) + let k = [] + l + call assert_equal([], k) + let k = l + [] + call assert_equal([], k) + call assert_equal(0, len(copy(l))) + call assert_equal(0, count(l, 5)) + call assert_equal([], deepcopy(l)) + call assert_equal(5, get(l, 2, 5)) + call assert_equal(-1, index(l, 2, 5)) + " call assert_equal(0, insert(l, 2, -1)) + call assert_equal(0, min(l)) + call assert_equal(0, max(l)) + " call assert_equal(0, remove(l, 0, 2)) + call assert_equal([], repeat(l, 2)) + " call assert_equal(0, reverse(l)) + " call assert_equal(0, sort(l)) + call assert_equal('[]', string(l)) + " call assert_equal(0, extend(l, l, 0)) + lockvar l + call assert_equal(1, islocked('l')) + unlockvar l +endfunc diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index d619ac0eb5..e0518de3c2 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -1,9 +1,5 @@ " Test for linebreak and list option (non-utf8) -" Nvim does not allow setting 'encoding', so skip this test. -finish - -set encoding=latin1 scriptencoding latin1 if !exists("+linebreak") || !has("conceal") @@ -46,6 +42,7 @@ func Test_set_linebreak() endfunc func Test_linebreak_with_list() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -217,6 +214,7 @@ func Test_norm_after_block_visual() endfunc func Test_block_replace_after_wrapping() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows() call setline(1, repeat("a", 150)) exe "norm! 0yypk147|\<C-V>jr0" diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index c4807797ff..0191dbf33e 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -427,6 +427,30 @@ func Test_error_in_map_expr() exe buf .. 'bwipe!' endfunc +func Test_expr_map_gets_cursor() + new + call setline(1, ['one', 'some w!rd']) + func StoreColumn() + let g:exprLine = line('.') + let g:exprCol = col('.') + return 'x' + endfunc + nnoremap <expr> x StoreColumn() + 2 + nmap ! f!<Ignore>x + call feedkeys("!", 'xt') + call assert_equal('some wrd', getline(2)) + call assert_equal(2, g:exprLine) + call assert_equal(7, g:exprCol) + + bwipe! + unlet g:exprLine + unlet g:exprCol + delfunc StoreColumn + nunmap x + nunmap ! +endfunc + " Test for mapping errors func Test_map_error() call assert_fails('unmap', 'E474:') diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index f9e40a9b43..2cbaf5cb76 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -59,9 +59,9 @@ func Test_matchadd_and_conceallevel_3() setlocal filetype=conf syntax on - 1put='# This is a Test' - " 1234567890123456 - let expect = '#ThisisaTest' + 1put='# This is a Test $' + " 1234567890123 + let expect = '#ThisisaTest$' call cursor(1, 1) call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'}) @@ -69,22 +69,25 @@ func Test_matchadd_and_conceallevel_3() let lnum = 2 call assert_equal(expect, Screenline(lnum)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) - call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 13)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 14)) call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 16)) " more matchadd() - " 1234567890123456 - let expect = '#Thisisa Test' + " 12345678901234 + let expect = '#Thisisa Test$' call matchadd('ErrorMsg', '\%2l Test', 20, -1, {'conceal': 'X'}) redraw! call assert_equal(expect, Screenline(lnum)) call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 2)) - call assert_equal(screenattr(lnum, 2) , screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7)) call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 10)) - call assert_equal(screenattr(lnum, 10), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 10), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 14)) call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16)) call assert_notequal(screenattr(lnum, 10), screenattr(lnum, 16)) @@ -132,15 +135,29 @@ func Test_syn_and_match_conceal() new setlocal concealcursor=n conceallevel=1 - 1put='# This is a Test' - " 1234567890123456 - let expect = '#ZThisZisZaZTest' + 1put='# This is a Test ' + let lnum = 2 call cursor(1, 1) + + " 123456789012345678 + let expect = '#ZThisZisZaZTestZZ' call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'}) + syntax match MyConceal /\%2l / conceal containedin=ALL + hi MyConceal ctermbg=4 ctermfg=2 + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + + syntax clear MyConceal syntax match MyConceal /\%2l / conceal containedin=ALL cchar=* redraw! - let lnum = 2 + call assert_equal(expect, Screenline(lnum)) call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) @@ -148,8 +165,8 @@ func Test_syn_and_match_conceal() call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) - " 1234567890123456 - let expect = '#*This*is*a*Test' + " 123456789012345678 + let expect = '#*This*is*a*Test**' call clearmatches() redraw! @@ -160,6 +177,48 @@ func Test_syn_and_match_conceal() call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + " 123456789012345678 + let expect = '#*ThisXis*a*Test**' + call matchadd('Conceal', '\%2l\%7c ', 10, -1, {'conceal': 'X'}) + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16)) + + " 123456789012345678 + let expect = '#*ThisXis*a*Test**' + call matchadd('ErrorMsg', '\%2l Test', 20, -1) + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18)) + call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19)) + + " 123456789012345678 + let expect = '# ThisXis a Test' + syntax clear MyConceal + syntax match MyConceal /\%2l / conceal containedin=ALL + redraw! + + call assert_equal(expect, Screenline(lnum)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 12)) + call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13)) + call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17)) + call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18)) + call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19)) + syntax off quit! endfunc diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index 055d944b15..de6d4aa359 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -11,7 +11,13 @@ func Test_load_menu() call assert_report('error while loading menus: ' . v:exception) endtry call assert_match('browse confirm w', execute(':menu File.Save')) + + let v:errmsg = '' + doautocmd LoadBufferMenu VimEnter + call assert_equal('', v:errmsg) + source $VIMRUNTIME/delmenu.vim + call assert_equal('', v:errmsg) endfunc func Test_translate_menu() diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 30239a90c2..08586dffe1 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,5 +1,6 @@ " Tests for :messages, :echomsg, :echoerr +source check.vim source shared.vim func Test_messages() @@ -31,6 +32,8 @@ func Test_messages() finally let &more = oldmore endtry + + call assert_fails('message 1', 'E474:') endfunc " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode @@ -75,7 +78,7 @@ func Test_echomsg() endfunc func Test_echoerr() - throw 'skipped: Nvim does not support test_ignore_error()' + CheckFunction test_ignore_error call test_ignore_error('IgNoRe') call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"')) call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"')) diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index f71da92bf8..8486f3ff68 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -291,6 +291,60 @@ 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 + +func Test_mkview_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + mkview! Xtestview + bdelete Xtestfile1 + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + +func Test_mksession_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + bdelete Xtestfile1 + mksession! Xtestview + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. @@ -373,6 +427,58 @@ func Test_mkview_no_file_name() %bwipe endfunc +func Test_mkview_loadview_jumplist() + set viewdir=Xviewdir + au BufWinLeave * silent mkview + " au BufWinEnter * silent loadview + + edit Xfile1 + call setline(1, ['a', 'bbbbbbb', 'c']) + normal j3l + call assert_equal([2, 4], getcurpos()[1:2]) + write + + edit Xfile2 + call setline(1, ['d', 'eeeeeee', 'f']) + normal j5l + call assert_equal([2, 6], getcurpos()[1:2]) + write + + edit Xfile3 + call setline(1, ['g', 'h', 'iiiii']) + normal jj3l + call assert_equal([3, 4], getcurpos()[1:2]) + write + + " The commented :au above was moved here so that :mkview (on BufWinLeave) can + " run before :loadview. This is needed because Nvim's :loadview raises E484 if + " the view can't be opened, while Vim's silently fails instead. + au BufWinEnter * silent loadview + + edit Xfile1 + call assert_equal([2, 4], getcurpos()[1:2]) + edit Xfile2 + call assert_equal([2, 6], getcurpos()[1:2]) + edit Xfile3 + call assert_equal([3, 4], getcurpos()[1:2]) + + exe "normal \<C-O>" + call assert_equal('Xfile2', expand('%')) + call assert_equal([2, 6], getcurpos()[1:2]) + exe "normal \<C-O>" + call assert_equal('Xfile1', expand('%')) + call assert_equal([2, 4], getcurpos()[1:2]) + + au! BufWinLeave + au! BufWinEnter + bwipe! + call delete('Xviewdir', 'rf') + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') + set viewdir& +endfunc + " A clean session (one empty buffer, one window, and one tab) should not " set any error messages when sourced because no commands should fail. func Test_mksession_no_errmsg() @@ -663,4 +769,27 @@ func Test_scrolloff() set sessionoptions& endfunc +func Test_altfile() + edit Xone + split Xtwo + edit Xtwoalt + edit # + wincmd w + edit Xonealt + edit # + mksession! Xtest_altfile + only + bwipe Xonealt + bwipe Xtwoalt + bwipe! + source Xtest_altfile + call assert_equal('Xone', bufname()) + call assert_equal('Xonealt', bufname('#')) + wincmd w + call assert_equal('Xtwo', bufname()) + call assert_equal('Xtwoalt', bufname('#')) + only + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 84d99ebb74..5aef33cb09 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -1,5 +1,7 @@ " Test for options +source check.vim + func Test_whichwrap() set whichwrap=b,s call assert_equal('b,s', &whichwrap) @@ -223,11 +225,18 @@ func Test_set_completion() " Expand files and directories. call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('./samples/ ./sautest/ ./screendump.vim ./setup.vim ./shared.vim', @:) + call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:) call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) set tags& + + " Expand values for 'filetype' + call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set filetype=sshdconfig', @:) + call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) + call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) endfunc func Test_set_errors() @@ -330,12 +339,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, @@ -441,6 +448,36 @@ func Test_backupskip() endif endfor + " Duplicates from environment variables should be filtered out (option has + " P_NODUP). Run this in a separate instance and write v:errors in a file, + " so that we see what happens on startup. + let after =<< trim [CODE] + let bsklist = split(&backupskip, ',') + call assert_equal(uniq(copy(bsklist)), bsklist) + call writefile(['errors:'] + v:errors, 'Xtestout') + qall + [CODE] + call writefile(after, 'Xafter') + " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"' + let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"' + + let saveenv = {} + for var in ['TMPDIR', 'TMP', 'TEMP'] + let saveenv[var] = getenv(var) + call setenv(var, '/duplicate/path') + endfor + + exe 'silent !' . cmd + call assert_equal(['errors:'], readfile('Xtestout')) + + " restore environment variables + for var in ['TMPDIR', 'TMP', 'TEMP'] + call setenv(var, saveenv[var]) + endfor + + call delete('Xtestout') + call delete('Xafter') + " Duplicates should be filtered out (option has P_NODUP) let backupskip = &backupskip set backupskip= @@ -606,6 +643,49 @@ func Test_opt_boolean() set number& endfunc +func Test_opt_winminheight_term() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + below sp | wincmd _ + below sp | wincmd _ + below sp | wincmd _ + below sp + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + +func Test_opt_winminheight_term_tabs() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + split + split + split + split + tabnew + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 @@ -615,4 +695,23 @@ func Test_isfname_with_options() setlocal keywordprg& endfunc +" Test that resetting laststatus does change scroll option +func Test_opt_reset_scroll() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + let vimrc =<< trim [CODE] + set scroll=2 + set laststatus=2 + [CODE] + call writefile(vimrc, 'Xscroll') + let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45}) + call term_sendkeys(buf, ":verbose set scroll?\n") + call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))}) + call assert_match('^\s*scroll=7$', term_getline(buf, 14)) + call StopVimInTerminal(buf) + + " clean up + call delete('Xscroll') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 4ee16558a0..9443958984 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -871,7 +871,7 @@ func Test_popup_complete_backwards_ctrl_p() endfunc fun! Test_complete_o_tab() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override let s:o_char_pressed = 0 fun! s:act_on_text_changed() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 48c0a83053..da949f5940 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2650,10 +2650,17 @@ 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() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override enew set incsearch call test_override("char_avail", 1) @@ -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_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 77a5153a81..6e6f91362b 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -97,7 +97,7 @@ func Do_test_quotestar_for_x11() if has('unix') && has('gui') && !has('gui_running') let @* = '' - " Running in a terminal and the GUI is avaiable: Tell the server to open + " Running in a terminal and the GUI is available: Tell the server to open " the GUI and check that the remote command still works. " Need to wait for the GUI to start up, otherwise the send hangs in trying " to send to the terminal window. diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 1bb2ee53de..cacdd68d10 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -1,5 +1,5 @@ " Tests for regexp in latin1 encoding -set encoding=latin1 +" set encoding=latin1 scriptencoding latin1 func s:equivalence_test() @@ -22,11 +22,13 @@ func s:equivalence_test() endfunc func Test_equivalence_re1() + throw 'skipped: Nvim does not support enc=latin1' set re=1 call s:equivalence_test() endfunc func Test_equivalence_re2() + throw 'skipped: Nvim does not support enc=latin1' set re=2 call s:equivalence_test() endfunc @@ -39,6 +41,17 @@ func Test_range_with_newline() bwipe! endfunc +func Test_pattern_compile_speed() + if !exists('+spellcapcheck') || !has('reltime') + return + endif + let start = reltime() + " this used to be very slow, not it should be about a second + set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179} + call assert_inrange(0.01, 10.0, reltimefloat(reltime(start))) + set spc= +endfunc + func Test_get_equi_class() new " Incomplete equivalence class caused invalid memory access @@ -87,6 +100,7 @@ func Test_multi_failure() endfunc func Test_recursive_addstate() + throw 'skipped: TODO: ' " This will call addstate() recursively until it runs into the limit. let lnum = search('\v((){328}){389}') call assert_equal(0, lnum) diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 8d2a768ba0..53069b3d31 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -109,6 +109,8 @@ func Test_recording_esc_sequence() bwipe! if exists('save_F2') let &t_F2 = save_F2 + else + set t_F2= endif endfunc diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim new file mode 100644 index 0000000000..e4228188bd --- /dev/null +++ b/src/nvim/testdir/test_rename.vim @@ -0,0 +1,119 @@ +" Test rename() + +func Test_rename_file_to_file() + call writefile(['foo'], 'Xrename1') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + " When the destination file already exists, it should be overwritten. + call writefile(['foo'], 'Xrename1') + call writefile(['bar'], 'Xrename2') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + call delete('Xrename2') +endfunc + +func Test_rename_file_ignore_case() + " With 'fileignorecase', renaming file will go through a temp file + " when the source and destination file only differ by case. + set fileignorecase + call writefile(['foo'], 'Xrename') + + call assert_equal(0, rename('Xrename', 'XRENAME')) + + call assert_equal(['foo'], readfile('XRENAME')) + + set fileignorecase& + call delete('XRENAME') +endfunc + +func Test_rename_same_file() + call writefile(['foo'], 'Xrename') + + " When the source and destination are the same file, nothing + " should be done. The source file should not be deleted. + call assert_equal(0, rename('Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call assert_equal(0, rename('./Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call delete('Xrename') +endfunc + +func Test_rename_dir_to_dir() + call mkdir('Xrenamedir1') + call writefile(['foo'], 'Xrenamedir1/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2')) + + call assert_equal('', glob('Xrenamedir1')) + call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile')) + + call delete('Xrenamedir2/Xrenamefile') + call delete('Xrenamedir2', 'd') +endfunc + +func Test_rename_same_dir() + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir', 'Xrenamedir')) + + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') +endfunc + +func Test_rename_copy() + " Check that when original file can't be deleted, rename() + " still succeeds but copies the file. + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + call setfperm('Xrenamedir', 'r-xr-xr-x') + + call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) + + if !has('win32') + " On Windows, the source file is removed despite + " its directory being made not writable. + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + endif + call assert_equal(['foo'], readfile('Xrenamefile')) + + call setfperm('Xrenamedir', 'rwxrwxrwx') + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') + call delete('Xrenamefile') +endfunc + +func Test_rename_fails() + throw 'skipped: TODO: ' + call writefile(['foo'], 'Xrenamefile') + + " Can't rename into a non-existing directory. + call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile')) + + " Can't rename a non-existing file. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2')) + call assert_equal('', glob('Xrenamefile2')) + + " When rename() fails, the destination file should not be deleted. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile')) + call assert_equal(['foo'], readfile('Xrenamefile')) + + " Can't rename to en empty file name. + call assert_notequal(0, rename('Xrenamefile', '')) + + call assert_fails('call rename("Xrenamefile", [])', 'E730') + call assert_fails('call rename(0z, "Xrenamefile")', 'E976') + + call delete('Xrenamefile') +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 0703a6b141..75d42b986b 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -2,13 +2,13 @@ source shared.vim source screendump.vim +source check.vim +" See test/functional/legacy/search_spec.lua func Test_search_cmdline() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -202,12 +202,11 @@ func Test_search_cmdline() bw! endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline2() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -351,7 +350,7 @@ func Test_searchc() endfunc func Cmdline3_prep() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -361,17 +360,15 @@ func Cmdline3_prep() endfunc func Incsearch_cleanup() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override set noincsearch call test_override("char_avail", 0) bw! endfunc func Test_search_cmdline3() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 " first match @@ -382,10 +379,8 @@ func Test_search_cmdline3() endfunc func Test_search_cmdline3s() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx') @@ -409,10 +404,8 @@ func Test_search_cmdline3s() endfunc func Test_search_cmdline3g() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":g/the\<c-l>/d\<cr>", 'tx') @@ -433,10 +426,8 @@ func Test_search_cmdline3g() endfunc func Test_search_cmdline3v() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":v/the\<c-l>/d\<cr>", 'tx') @@ -450,12 +441,11 @@ func Test_search_cmdline3v() call Incsearch_cleanup() endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline4() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -487,9 +477,8 @@ func Test_search_cmdline4() endfunc func Test_search_cmdline5() - if !exists('+incsearch') - return - endif + CheckOption incsearch + " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work " regardless char_avail. new @@ -506,8 +495,48 @@ func Test_search_cmdline5() bw! endfunc +func Test_search_cmdline6() + " Test that consecutive matches + " are caught by <c-g>/<c-t> + CheckFunction test_override + CheckOption incsearch + + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, [' bbvimb', '']) + set incsearch + " first match + norm! gg0 + call feedkeys("/b\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + " second match + norm! gg0 + call feedkeys("/b\<c-g>\<cr>", 'tx') + call assert_equal([0,1,3,0], getpos('.')) + " third match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " first match again + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + set nowrapscan + " last match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " clean up + set wrapscan&vim + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + func Test_search_cmdline7() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " Test that pressing <c-g> in an empty command line " does not move the cursor if !exists('+incsearch') @@ -601,26 +630,226 @@ func Test_search_regexp() enew! endfunc -" Test for search('multi-byte char', 'bce') -func Test_search_multibyte() - let save_enc = &encoding - set encoding=utf8 - enew! - call append('$', 'A') - call cursor(2, 1) - call assert_equal(2, search('A', 'bce', line('.'))) - enew! - let &encoding = save_enc +func Test_search_cmdline_incsearch_highlight() + CheckFunction test_override + CheckOption incsearch + + set incsearch hlsearch + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third']) + + 1 + call feedkeys("/second\<cr>", 'tx') + call assert_equal('second', @/) + call assert_equal(' 2 the second', getline('.')) + + " Canceling search won't change @/ + 1 + let @/ = 'last pattern' + call feedkeys("/third\<C-c>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<Esc>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/3\<bs>\<bs>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx') + call assert_equal('last pattern', @/) + + " clean up + set noincsearch nohlsearch + bw! endfunc -" Similar to Test_incsearch_substitute() but with a screendump halfway. -func Test_incsearch_substitute_dump() - if !exists('+incsearch') +func Test_search_cmdline_incsearch_highlight_attr() + CheckOption incsearch + CheckFeature terminal + CheckNotGui + + let h = winheight(0) + if h < 3 return endif + + " Prepare buffer text + let lines = ['abb vim vim vi', 'vimvivim'] + call writefile(lines, 'Xsearch.txt') + let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) + + call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) + " wait for vim to complete initialization + call term_wait(buf) + + " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight + call term_sendkeys(buf, ":set incsearch hlsearch\<cr>") + call term_sendkeys(buf, '/b') + call term_wait(buf, 200) + let screen_line1 = term_scrape(buf, 1) + call assert_true(len(screen_line1) > 2) + " a0: attr_normal + let a0 = screen_line1[0].attr + " a1: attr_incsearch + let a1 = screen_line1[1].attr + " a2: attr_hlsearch + let a2 = screen_line1[2].attr + call assert_notequal(a0, a1) + call assert_notequal(a0, a2) + call assert_notequal(a1, a2) + call term_sendkeys(buf, "\<cr>gg0") + + " Test incremental highlight search + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + " Buffer: + " abb vim vim vi + " vimvivim + " Search: /vim + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-g> + call term_sendkeys(buf, "\<C-g>\<C-g>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-t> + call term_sendkeys(buf, "\<C-t>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight) + call term_sendkeys(buf, "\<cr>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight) + call term_sendkeys(buf, ":1\<cr>") + call term_sendkeys(buf, ":set nohlsearch\<cr>") + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0] + let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + call delete('Xsearch.txt') + + call delete('Xsearch.txt') + bwipe! +endfunc + +func Test_incsearch_cmdline_modifier() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + call setline(1, ['foo']) + set incsearch + " Test that error E14 does not occur in parsing command modifier. + call feedkeys("V:tab", 'tx') + + call Incsearch_cleanup() +endfunc + +func Test_incsearch_scrolling() if !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps' endif + call assert_equal(0, &scrolloff) + call writefile([ + \ 'let dots = repeat(".", 120)', + \ 'set incsearch cmdheight=2 scrolloff=0', + \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', + \ 'normal gg', + \ 'redraw', + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '/') + sleep 100m + call term_sendkeys(buf, 't') + sleep 100m + call term_sendkeys(buf, 'a') + sleep 100m + call term_sendkeys(buf, 'r') + sleep 100m + call term_sendkeys(buf, 'g') + call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + +func Test_incsearch_search_dump() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 8)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ '3', + \ ], 'Xis_search_script') + let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + " Need to send one key at a time to force a redraw. + call term_sendkeys(buf, '/fo') + call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) + call term_sendkeys(buf, "\<Esc>") + sleep 100m + + call term_sendkeys(buf, '/\v') + call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_search_script') +endfunc + +func Test_incsearch_substitute() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + set incsearch + for n in range(1, 10) + call setline(n, 'foo ' . n) + endfor + 4 + call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') + call assert_equal('foo 3', getline(3)) + call assert_equal('xxx 4', getline(4)) + call assert_equal('xxx 5', getline(5)) + call assert_equal('xxx 6', getline(6)) + call assert_equal('foo 7', getline(7)) + + call Incsearch_cleanup() +endfunc + +" Similar to Test_incsearch_substitute() but with a screendump halfway. +func Test_incsearch_substitute_dump() + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'for n in range(1, 10)', @@ -726,14 +955,58 @@ func Test_incsearch_substitute_dump() call delete('Xis_subst_script') endfunc +func Test_incsearch_highlighting() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch', + \ 'call setline(1, "hello/there")', + \ ], 'Xis_subst_hl_script') + let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Using a different search delimiter should still highlight matches + " that contain a '/'. + call term_sendkeys(buf, ":%s;ello/the") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {}) + call term_sendkeys(buf, "<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_subst_hl_script') +endfunc + +func Test_incsearch_with_change() + CheckFeature timers + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["one", "two ------ X", "three"])', + \ 'call timer_start(200, { _ -> setline(2, "x")})', + \ ], 'Xis_change_script') + let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Highlight X, it will be deleted by the timer callback. + call term_sendkeys(buf, ':%s/X') + call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_change_script') +endfunc + " Similar to Test_incsearch_substitute_dump() for :sort func Test_incsearch_sort_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -757,12 +1030,9 @@ endfunc " Similar to Test_incsearch_substitute_dump() for :vimgrep famiry func Test_incsearch_vimgrep_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -798,10 +1068,9 @@ func Test_incsearch_vimgrep_dump() endfunc func Test_keep_last_search_pattern() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + new call setline(1, ['foo', 'foo', 'foo']) set incsearch @@ -820,10 +1089,9 @@ func Test_keep_last_search_pattern() endfunc func Test_word_under_cursor_after_match() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + new call setline(1, 'foo bar') set incsearch @@ -840,10 +1108,9 @@ func Test_word_under_cursor_after_match() endfunc func Test_subst_word_under_cursor() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + new call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)']) set incsearch @@ -857,132 +1124,8 @@ func Test_subst_word_under_cursor() set noincsearch endfunc -func Test_incsearch_with_change() - if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing' - endif - - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'call setline(1, ["one", "two ------ X", "three"])', - \ 'call timer_start(200, { _ -> setline(2, "x")})', - \ ], 'Xis_change_script') - let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 300m - - " Highlight X, it will be deleted by the timer callback. - call term_sendkeys(buf, ':%s/X') - call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_change_script') -endfunc - -func Test_incsearch_cmdline_modifier() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - call setline(1, ['foo']) - set incsearch - " Test that error E14 does not occur in parsing command modifier. - call feedkeys("V:tab", 'tx') - - call Incsearch_cleanup() -endfunc - -func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - return - endif - call assert_equal(0, &scrolloff) - call writefile([ - \ 'let dots = repeat(".", 120)', - \ 'set incsearch cmdheight=2 scrolloff=0', - \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', - \ 'normal gg', - \ 'redraw', - \ ], 'Xscript') - let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) - " Need to send one key at a time to force a redraw - call term_sendkeys(buf, '/') - sleep 100m - call term_sendkeys(buf, 't') - sleep 100m - call term_sendkeys(buf, 'a') - sleep 100m - call term_sendkeys(buf, 'r') - sleep 100m - call term_sendkeys(buf, 'g') - call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) - - call term_sendkeys(buf, "\<Esc>") - call StopVimInTerminal(buf) - call delete('Xscript') -endfunc - -func Test_incsearch_search_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - return - endif - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'for n in range(1, 8)', - \ ' call setline(n, "foo " . n)', - \ 'endfor', - \ '3', - \ ], 'Xis_search_script') - let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 100m - - " Need to send one key at a time to force a redraw. - call term_sendkeys(buf, '/fo') - call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) - call term_sendkeys(buf, "\<Esc>") - sleep 100m - - call term_sendkeys(buf, '/\v') - call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_search_script') -endfunc - -func Test_incsearch_substitute() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - set incsearch - for n in range(1, 10) - call setline(n, 'foo ' . n) - endfor - 4 - call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') - call assert_equal('foo 3', getline(3)) - call assert_equal('xxx 4', getline(4)) - call assert_equal('xxx 5', getline(5)) - call assert_equal('xxx 6', getline(6)) - call assert_equal('foo 7', getline(7)) - - call Incsearch_cleanup() -endfunc - func Test_incsearch_substitute_long_line() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override("char_avail", 1) set incsearch @@ -997,9 +1140,8 @@ func Test_incsearch_substitute_long_line() endfunc func Test_search_undefined_behaviour() - if !has("terminal") - return - endif + CheckFeature terminal + let h = winheight(0) if h < 3 return @@ -1015,6 +1157,18 @@ func Test_search_undefined_behaviour2() call search("\%UC0000000") endfunc +" Test for search('multi-byte char', 'bce') +func Test_search_multibyte() + let save_enc = &encoding + set encoding=utf8 + enew! + call append('$', 'A') + call cursor(2, 1) + call assert_equal(2, search('A', 'bce', line('.'))) + enew! + let &encoding = save_enc +endfunc + " This was causing E874. Also causes an invalid read? func Test_look_behind() new @@ -1053,9 +1207,8 @@ func Test_search_Ctrl_L_combining() " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" " Those should also appear on the commandline - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 let bufcontent = ['', 'Miạ̀́̇m'] @@ -1104,10 +1257,9 @@ func Test_one_error_msg() endfunc func Test_incsearch_add_char_under_cursor() - throw 'skipped: Nvim does not support test_override()' - if !exists('+incsearch') - return - endif + CheckFunction test_override + CheckOption incsearch + set incsearch new call setline(1, ['find match', 'anything']) @@ -1192,4 +1344,36 @@ func Test_search_smartcase_utf8() close! endfunc +func Test_incsearch_highlighting_newline() + CheckRunVimInTerminal + CheckOption incsearch + CheckScreendump + new + call test_override("char_avail", 1) + + let commands =<< trim [CODE] + set incsearch nohls + call setline(1, ['test', 'xxx']) + [CODE] + call writefile(commands, 'Xincsearch_nl') + let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10}) + call term_sendkeys(buf, '/test') + call VerifyScreenDump(buf, 'Test_incsearch_newline1', {}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '\n') + call VerifyScreenDump(buf, 'Test_incsearch_newline2', {}) + call term_sendkeys(buf, 'x') + call VerifyScreenDump(buf, 'Test_incsearch_newline3', {}) + call term_sendkeys(buf, 'x') + call VerifyScreenDump(buf, 'Test_incsearch_newline4', {}) + call term_sendkeys(buf, "\<CR>") + call VerifyScreenDump(buf, 'Test_incsearch_newline5', {}) + call StopVimInTerminal(buf) + + " clean up + call delete('Xincsearch_nl') + call test_override("char_avail", 0) + bw +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim new file mode 100644 index 0000000000..ec357dac88 --- /dev/null +++ b/src/nvim/testdir/test_shift.vim @@ -0,0 +1,117 @@ +" Test shifting lines with :> and :< + +source check.vim + +func Test_ex_shift_right() + set shiftwidth=2 + + " shift right current line. + call setline(1, range(1, 5)) + 2 + > + 3 + >> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 4)) + 2,3>> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 4)) + 2>3 + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ ' 4', + \ '5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_left() + set shiftwidth=2 + + call setline(1, range(1, 5)) + %>>> + + " left shift current line. + 2< + 3<< + 4<<<<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ '4', + \ ' 5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 5)) + %>>> + 2,3<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 5)) + %>>> + 2<<3 + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_rightleft() + CheckFeature rightleft + + set shiftwidth=2 rightleft + + call setline(1, range(1, 4)) + 2,3<< + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + 3,4> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + bw! + set rightleft& shiftwidth& +endfunc + +func Test_ex_shift_errors() + call assert_fails('><', 'E488:') + call assert_fails('<>', 'E488:') + + call assert_fails('>!', 'E477:') + call assert_fails('<!', 'E477:') + + " call assert_fails('2,1>', 'E493:') + call assert_fails('execute "2,1>"', 'E493:') + " call assert_fails('2,1<', 'E493:') + call assert_fails('execute "2,1<"', 'E493:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 4bbd722fdb..9753100375 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1,8 +1,7 @@ " Test for signs -if !has('signs') - finish -endif +source check.vim +CheckFeature signs source screendump.vim @@ -135,7 +134,7 @@ func Test_sign() sign define Sign5 text=X\ linehl=Comment sign undefine Sign5 - sign define Sign5 linehl=Comment text=X\ + sign define Sign5 linehl=Comment text=X\ sign undefine Sign5 " define sign with backslash @@ -416,7 +415,7 @@ func Test_sign_funcs() " Tests for invalid arguments to sign_define() call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:') " call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:') - call assert_fails('call sign_define([])', 'E730:') + call assert_fails('call sign_define({})', 'E731:') call assert_fails('call sign_define("sign6", [])', 'E715:') " Tests for sign_getdefined() @@ -445,7 +444,7 @@ func Test_sign_funcs() call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:') call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:') call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', - \ 'E474:') + \ 'E715:') call assert_fails('call sign_place(-1, "", "sign1", "Xsign", \ {"lnum" : 30})', 'E474:') call assert_fails('call sign_place(10, "", "xsign1x", "Xsign", @@ -461,11 +460,11 @@ func Test_sign_funcs() call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', \ 'E158:') call assert_fails('call sign_place(21, "", "sign1", "Xsign", - \ {"lnum" : -1})', 'E885:') + \ {"lnum" : -1})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : 0})', 'E885:') + \ {"lnum" : 0})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : []})', 'E745:') + \ {"lnum" : []})', 'E474:') call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) " Tests for sign_getplaced() @@ -505,11 +504,21 @@ func Test_sign_funcs() \ {'id' : 20, 'buffer' : 200})", 'E158:') call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:') + call sign_unplace('*') + + " Test for modifying a placed sign + call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20})) + call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign')) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', + \ 'priority' : 10}]}], + \ sign_getplaced()) + " Tests for sign_undefine() call assert_equal(0, sign_undefine("sign1")) call assert_equal([], sign_getdefined("sign1")) call assert_fails('call sign_undefine("none")', 'E155:') - call assert_fails('call sign_undefine([])', 'E730:') + call assert_fails('call sign_undefine({})', 'E731:') " Test for using '.' as the line number for sign_place() call Sign_define_ignore_error("sign1", attr) @@ -645,7 +654,7 @@ func Test_sign_group() call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs) " Error case - call assert_fails("call sign_unplace([])", 'E474:') + call assert_fails("call sign_unplace({})", 'E474:') " Place a sign in the global group and try to delete it using a group call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10})) @@ -1132,7 +1141,7 @@ func Test_sign_unplace() endfunc " Tests for auto-generating the sign identifier -func Test_sign_id_autogen() +func Test_aaa_sign_id_autogen() enew | only call sign_unplace('*') call sign_undefine() @@ -1541,7 +1550,7 @@ endfunc " Tests for memory allocation failures in sign functions func Test_sign_memfailures() - throw 'skipped: Nvim does not support test_alloc_fail()' + CheckFunction test_alloc_fail call writefile(repeat(["Sun is shining"], 30), "Xsign") edit Xsign @@ -1619,26 +1628,7 @@ func Test_sign_lnum_adjust() " Delete the line with the sign call deletebufline('', 4) let l = sign_getplaced(bufnr('')) - call assert_equal(4, l[0].signs[0].lnum) - - " Undo the delete operation - undo - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) - - " Break the undo - let &undolevels=&undolevels - - " Delete few lines at the end of the buffer including the line with the sign - " Sign line number should not change (as it is placed outside of the buffer) - call deletebufline('', 3, 6) - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) - - " Undo the delete operation. Sign should be restored to the previous line - undo - let l = sign_getplaced(bufnr('')) - call assert_equal(5, l[0].signs[0].lnum) + call assert_equal(0, len(l[0].signs)) sign unplace * group=* sign undefine sign1 @@ -1869,3 +1859,121 @@ func Test_sign_numcol() set number& enew! | close endfunc + +" Test for managing multiple signs using the sign functions +func Test_sign_funcs_multi() + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + let bnum = bufnr('') + + " Define multiple signs at once + call assert_equal([0, 0, 0, 0], sign_define([ + \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}])) + + " Negative cases for sign_define() + call assert_equal([], sign_define([])) + call assert_equal([-1], sign_define([{}])) + call assert_fails('call sign_define([6])', 'E715:') + call assert_fails('call sign_define(["abc"])', 'E715:') + call assert_fails('call sign_define([[]])', 'E715:') + + " Place multiple signs at once with specific sign identifier + let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1', + \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50}, + \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2', + \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100}, + \ {'id' : 3, 'group' : '', 'name' : 'sign3', + \ 'buffer' : 'Xsign', 'lnum' : 11}]) + call assert_equal([1, 2, 3], l) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11, + \ 'group' : 'g2', 'priority' : 100}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : 'g1', 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + call sign_unplace('*') + + " Place multiple signs at once with auto-generated sign identifier + call assert_equal([1, 1, 5], sign_placelist([ + \ {'group' : 'g1', 'name' : 'sign1', + \ 'buffer' : 'Xsign', 'lnum' : 11}, + \ {'group' : 'g2', 'name' : 'sign2', + \ 'buffer' : 'Xsign', 'lnum' : 11}, + \ {'group' : '', 'name' : 'sign3', + \ 'buffer' : 'Xsign', 'lnum' : 11}])) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}, + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11, + \ 'group' : 'g2', 'priority' : 10}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : 'g1', 'priority' : 10}], s[0].signs) + + " Change an existing sign without specifying the group + call assert_equal([5], sign_placelist([ + \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}])) + let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''}) + call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + " Place a sign using '.' as the line number + call cursor(23, 1) + call assert_equal([7], sign_placelist([ + \ {'id' : 7, 'name' : 'sign1', 'buffer' : '%', 'lnum' : '.'}])) + let s = sign_getplaced('%', {'lnum' : '.'}) + call assert_equal([{'id' : 7, 'name' : 'sign1', 'lnum' : 23, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + " Place sign without a sign name + call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign', + \ 'lnum' : 12, 'group' : ''}])) + + " Place sign without a buffer + call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1', + \ 'lnum' : 12, 'group' : ''}])) + + " Invalid arguments + call assert_equal([], sign_placelist([])) + call assert_fails('call sign_placelist({})', "E714:") + call assert_fails('call sign_placelist([[]])', "E715:") + call assert_fails('call sign_placelist(["abc"])', "E715:") + call assert_fails('call sign_placelist([100])', "E715:") + + " Unplace multiple signs + call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5}, + \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}])) + + " Invalid arguments + call assert_equal([], sign_unplacelist([])) + call assert_fails('call sign_unplacelist({})', "E714:") + call assert_fails('call sign_unplacelist([[]])', "E715:") + call assert_fails('call sign_unplacelist(["abc"])', "E715:") + call assert_fails('call sign_unplacelist([100])', "E715:") + call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474') + + call assert_equal([0, 0, 0, 0], + \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4'])) + call assert_equal([], sign_getdefined()) + + " Invalid arguments + call assert_equal([], sign_undefine([])) + call assert_fails('call sign_undefine([[]])', 'E730:') + call assert_fails('call sign_undefine([{}])', 'E731:') + call assert_fails('call sign_undefine(["1abc2"])', 'E155:') + + call sign_unplace('*') + call sign_undefine() + enew! + call delete("Xsign") +endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index e6ad92f483..e0dc0e0075 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -111,10 +111,9 @@ func Test_pack_in_rtp_when_plugins_run() endfunc func Test_help_arg() - if !has('unix') && has('gui') - " this doesn't work with gvim on MS-Windows - return - endif + " This does not work with a GUI-only binary, such as on MS-Windows. + CheckAnyOf Unix NotGui + if RunVim([], [], '--help >Xtestout') let lines = readfile('Xtestout') call assert_true(len(lines) > 20) @@ -412,6 +411,134 @@ func Test_A_F_H_arg() call delete('Xtestout') endfunc +" Test the --echo-wid argument (for GTK GUI only). +func Test_echo_wid() + CheckCanRunGui + CheckFeature gui_gtk + + if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid') + let lines = readfile('Xtest_echo_wid') + call assert_equal(1, len(lines)) + call assert_match('^WID: \d\+$', lines[0]) + endif + + call delete('Xtest_echo_wid') +endfunction + +" Test the -reverse and +reverse arguments (for GUI only). +func Test_reverse() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + let after =<< trim [CODE] + call writefile([&background], "Xtest_reverse") + qall + [CODE] + if RunVim([], after, '-f -g -reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g +reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_reverse') +endfunc + +" Test the -background and -foreground arguments (for GUI only). +func Test_background_foreground() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + " Is there a better way to check the effect of -background & -foreground + " other than merely looking at &background (dark or light)? + let after =<< trim [CODE] + call writefile([&background], "Xtest_fg_bg") + qall + [CODE] + if RunVim([], after, '-f -g -background darkred -foreground yellow') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g -background ivory -foreground darkgreen') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_fg_bg') +endfunc + +" Test the -font argument (for GUI only). +func Test_font() + CheckCanRunGui + CheckNotMSWindows + + if has('gui_gtk') + let font = 'Courier 14' + elseif has('gui_motif') || has('gui_athena') + let font = '-misc-fixed-bold-*' + else + throw 'Skipped: test does not set a valid font for this GUI' + endif + + let after =<< trim [CODE] + call writefile([&guifont], "Xtest_font") + qall + [CODE] + + if RunVim([], after, '--nofork -g -font "' .. font .. '"') + let lines = readfile('Xtest_font') + call assert_equal([font], lines) + endif + + call delete('Xtest_font') +endfunc + +" Test the -geometry argument (for GUI only). +func Test_geometry() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + if has('gui_motif') || has('gui_athena') + " FIXME: With GUI Athena or Motif, the value of getwinposx(), + " getwinposy() and getwinpos() do not match exactly the + " value given in -geometry. Why? + " So only check &columns and &lines for those GUIs. + let after =<< trim [CODE] + call writefile([&columns, &lines], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13'], lines) + endif + else + let after =<< trim [CODE] + call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines) + endif + endif + + call delete('Xtest_geometry') +endfunc + +" Test the -iconic argument (for GUI only). +func Test_iconic() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + call RunVim([], [], '-f -g -iconic -cq') + + " TODO: currently only start vim iconified, but does not + " check that vim is iconified. How could this be checked? +endfunc + + func Test_invalid_args() if !has('unix') || has('gui_running') " can't get output of Vim. @@ -687,6 +814,34 @@ func Test_v_argv() call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:]) endfunc +" Test for the '-t' option to jump to a tag +func Test_t_arg() + let before =<< trim [CODE] + set tags=Xtags + [CODE] + let after =<< trim [CODE] + let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.') + call writefile([s], "Xtestout") + qall + [CODE] + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfile1\t/^ \\zsfirst$/", + \ "second\tXfile1\t/^ \\zssecond$/", + \ "third\tXfile1\t/^ \\zsthird$/"], + \ 'Xtags') + call writefile([' first', ' second', ' third'], 'Xfile1') + + for t_arg in ['-t second', '-tsecond'] + if RunVim(before, after, '-t second') + call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) + call delete('Xtestout') + endif + endfor + + call delete('Xtags') + call delete('Xfile1') +endfunc + " Test the '-T' argument which sets the 'term' option. func Test_T_arg() throw 'skipped: Nvim does not support "-T" argument' @@ -735,6 +890,66 @@ func Test_x_arg() call delete('Xtest_x_arg') endfunc +" Test for --not-a-term avoiding escape codes. +func Test_not_a_term() + CheckUnix + CheckNotGui + + if &shellredir =~ '%s' + let redir = printf(&shellredir, 'Xvimout') + else + let redir = &shellredir .. ' Xvimout' + endif + + " Without --not-a-term there are a few escape sequences. + " This will take 2 seconds because of the missing --not-a-term + let cmd = GetVimProg() .. ' --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_match("\<Esc>", readfile('Xvimout')->join()) + call assert_match("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') + + " With --not-a-term there are no escape sequences. + let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) + call assert_notmatch("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') +endfunc + + +" Test for the "-w scriptout" argument +func Test_w_arg() + " Can't catch the output of gvim. + CheckNotGui + + call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'b') + if RunVim([], [], '-s Xscriptin -w Xscriptout') + call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout')) + call delete('Xscriptout') + endif + call delete('Xscriptin') + + " Test for failing to open the script output file. This test works only when + " the language is English. + if !has('win32') && (v:lang == "C" || v:lang =~ '^[Ee]n') + call mkdir("Xdir") + let m = system(GetVimCommand() .. " -w Xdir") + call assert_equal("Cannot open for script output: \"Xdir\"\n", m) + call delete("Xdir", 'rf') + endif + + " A number argument sets the 'window' option + call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b') + for w_arg in ['-w 17', '-w17'] + if RunVim([], [], '-s Xscriptin ' .. w_arg) + call assert_equal(["window 17"], readfile('Xresult'), w_arg) + call delete('Xresult') + endif + endfor + call delete('Xscriptin') +endfunc + " Test starting vim with various names: vim, ex, view, evim, etc. func Test_progname() CheckUnix @@ -777,17 +992,12 @@ func Test_progname() 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 + let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview')) + + if empty($DISPLAY) && run_with_gui + " Can't run gvim, gview (etc.) if $DISPLAY is not setup. + continue + endif exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname @@ -801,7 +1011,15 @@ func Test_progname() 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) + " GUI motif can output some warnings like this: + " Warning: + " Name: subMenu + " Class: XmCascadeButton + " Illegal mnemonic character; Could not convert X KEYSYM to a keycode + " So don't check that stderr is empty with GUI Motif. + if run_with_gui && !has('gui_motif') + call assert_equal('', stdout_stderr, progname) + endif call assert_equal(expectations[progname], readfile('Xprogname_out'), progname) endif diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 4e38f7ebd8..f5b6446108 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -1,12 +1,12 @@ " Test 'statusline' " " Not tested yet: -" %a " %N " %T " %X source view_util.vim +source check.vim source term_util.vim func s:get_statusline() @@ -61,7 +61,19 @@ func Test_statusline_will_be_disabled_with_error() endfunc func Test_statusline() - new Xstatusline + CheckFeature quickfix + + " %a: Argument list ({current} of {max}) + set statusline=%a + call assert_match('^\s*$', s:get_statusline()) + arglocal a1 a2 + rewind + call assert_match('^ (1 of 2)\s*$', s:get_statusline()) + next + call assert_match('^ (2 of 2)\s*$', s:get_statusline()) + e Xstatusline + call assert_match('^ ((2) of 2)\s*$', s:get_statusline()) + only set laststatus=2 set splitbelow @@ -428,6 +440,28 @@ func Test_statusline_removed_group() call delete('XTest_statusline') endfunc +func Test_statusline_using_mode() + CheckScreendump + + let lines =<< trim END + setlocal statusline=-%{mode()}- + split + setlocal statusline=+%{mode()}+ + END + call writefile(lines, 'XTest_statusline') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50}) + call VerifyScreenDump(buf, 'Test_statusline_mode_1', {}) + + call term_sendkeys(buf, ":") + call VerifyScreenDump(buf, 'Test_statusline_mode_2', {}) + + " clean up + call term_sendkeys(buf, "close\<CR>") + call StopVimInTerminal(buf) + call delete('XTest_statusline') +endfunc + func Test_statusline_after_split_vsplit() only diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 2a27f7a3a1..cc3bfe9f7f 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -746,3 +746,12 @@ func Test_sub_beyond_end() call assert_equal('#', getline(1)) bwipe! endfunc + +func Test_submatch_list_concatenate() + let pat = 'A\(.\)' + let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])} + " call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") + call assert_equal(substitute('A1', pat, Rep, ''), "[['A1'], ['1']]") +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 4cf0e983b0..875e23894f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -24,13 +24,39 @@ func GetSyntaxItem(pat) return c endfunc +func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") + " Assert that the characters starting at a given (line, col) + " sequentially match the expected highlight groups. + " If groups are provided as a string, each character is assumed to be a + " group and spaces represent no group, useful for visually describing tests. + let l:expectedGroups = type(a:expected) == v:t_string + "\ ? a:expected->split('\zs')->map({_, v -> trim(v)}) + \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)}) + \ : a:expected + let l:errors = 0 + " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") + let l:msg = (empty(a:msg) ? "" : a:msg .. ": ") + \ .. "Wrong highlight group at " .. a:lnum .. "," + + " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) + " let l:errors += synID(a:lnum, l:i, a:trans) + " \ ->synIDattr("name") + " \ ->assert_equal(l:expectedGroups[l:i - 1], + for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1) + let l:errors += + \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"), + \ l:expectedGroups[l:i - 1], + \ l:msg .. l:i) + endfor +endfunc + func Test_syn_iskeyword() new call setline(1, [ \ 'CREATE TABLE FOOBAR(', \ ' DLTD_BY VARCHAR2(100)', \ ');', - \ '']) + \ '']) syntax on set ft=sql @@ -521,7 +547,7 @@ func Test_synstack_synIDtrans() norm f/ call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) - call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) norm fA call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) @@ -707,3 +733,22 @@ func Test_syntax_foldlevel() quit! endfunc + +func Test_syn_include_contains_TOP() + let l:case = "TOP in included syntax means its group list name" + new + syntax include @INCLUDED syntax/c.vim + syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED + + call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ]) + let l:expected = ["cCppOutIf2"] + eval AssertHighlightGroups(3, 1, l:expected, 1) + " cCppOutElse has contains=TOP + let l:expected = ["cType"] + eval AssertHighlightGroups(5, 1, l:expected, 1, l:case) + syntax clear + bw! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 9cf8690d57..6bbe714d19 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -93,7 +93,6 @@ function! Test_system_exmode() endfunc func Test_system_with_shell_quote() - throw 'skipped: enable after porting method patches' CheckMSWindows call mkdir('Xdir with spaces', 'p') @@ -122,7 +121,8 @@ func Test_system_with_shell_quote() let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) try - let out = 'echo 123'->system() + " let out = 'echo 123'->system() + let out = system('echo 123') catch call assert_report(printf('%s: %s', msg, v:exception)) continue diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim index b847dbd962..b8e8dfe062 100644 --- a/src/nvim/testdir/test_tab.vim +++ b/src/nvim/testdir/test_tab.vim @@ -1,3 +1,4 @@ +" Various tests for inserting a Tab. " Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set. " Also test that dv_ works correctly @@ -43,3 +44,47 @@ func Test_smarttab() enew! set expandtab& smartindent& copyindent& ts& sw& sts& endfunc + +func Test_softtabstop() + new + set sts=0 sw=0 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x\tx", getline(1)) + + call setline(1, '') + set sts=4 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, '') + set sts=-1 sw=4 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=0 sw=0 backspace=start + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=4 + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=-1 sw=4 + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x') + set sts=-1 sw=0 smarttab + exe "normal I\<Tab>\<Esc>" + call assert_equal("\tx", getline(1)) + + call setline(1, 'x') + exe "normal I\<Tab>\<BS>\<Esc>" + call assert_equal("x", getline(1)) + + set sts=0 sw=0 backspace& nosmarttab + bwipe! +endfunc diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 2b6a89647e..b261b96c3b 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -142,9 +142,6 @@ endfunc " Test autocommands function Test_tabpage_with_autocmd() - if !has('autocmd') - return - endif command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args> augroup TestTabpageGroup au! diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index 242aa3a235..ffc1d63b90 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -43,12 +43,24 @@ func Test_tagfunc() call assert_equal('one', g:tagfunc_args[0]) call assert_equal('c', g:tagfunc_args[1]) + let g:tagfunc_args=[] + execute "tag /foo$" + call assert_equal('foo$', g:tagfunc_args[0]) + call assert_equal('r', g:tagfunc_args[1]) + set cpt=t let g:tagfunc_args=[] execute "normal! i\<c-n>\<c-y>" - call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('\<\k\k', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) call assert_equal('nothing1', getline('.')[0:7]) + let g:tagfunc_args=[] + execute "normal! ono\<c-n>\<c-n>\<c-y>" + call assert_equal('\<no', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) + call assert_equal('nothing2', getline('.')[0:7]) + func BadTagFunc1(...) return 0 endfunc @@ -81,4 +93,28 @@ func Test_tagfunc() call delete('Xfile1') endfunc +" Test for modifying the tag stack from a tag function and jumping to a tag +" from a tag function +func Test_tagfunc_settagstack() + func Mytagfunc1(pat, flags, info) + call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]}) + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc1 + call writefile([''], 'Xtest') + call assert_fails('tag xyz', 'E986:') + + func Mytagfunc2(pat, flags, info) + tag test_tag + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc2 + call assert_fails('tag xyz', 'E986:') + + call delete('Xtest') + set tagfunc& + delfunc Mytagfunc1 + delfunc Mytagfunc2 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 7057cdefb2..9f02af7d8e 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -609,6 +609,295 @@ func Test_tagline() set tags& endfunc +" Test for expanding environment variable in a tag file name +func Test_tag_envvar() + call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags') + set tags=Xtags + + let $FOO='TagTestEnv' + + let caught_exception = v:false + try + tag Func1 + catch /E429:/ + call assert_match('E429:.*"TagTestEnv".*', v:exception) + let caught_exception = v:true + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') + unlet $FOO +endfunc + +" Test for :ptag +func Test_ptag() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "second\tXfile1\t2", + \ "third\tXfile1\t3",], + \ 'Xtags') + set tags=Xtags + call writefile(['first', 'second', 'third'], 'Xfile1') + + enew | only + ptag third + call assert_equal(2, winnr()) + call assert_equal(2, winnr('$')) + call assert_equal(1, getwinvar(1, '&previewwindow')) + call assert_equal(0, getwinvar(2, '&previewwindow')) + wincmd w + call assert_equal(3, line('.')) + + " jump to the tag again + ptag third + call assert_equal(3, line('.')) + + " close the preview window + pclose + call assert_equal(1, winnr('$')) + + call delete('Xfile1') + call delete('Xtags') + set tags& +endfunc + +" Tests for guessing the tag location +func Test_tag_guess() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "func1\tXfoo\t/^int func1(int x)/", + \ "func2\tXfoo\t/^int func2(int y)/", + \ "func3\tXfoo\t/^func3/", + \ "func4\tXfoo\t/^func4/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + + int FUNC1 (int x) { } + int + func2 (int y) { } + int * func3 () { } + + [CODE] + call writefile(code, 'Xfoo') + + let v:statusmsg = '' + ta func1 + call assert_match('E435:', v:statusmsg) + call assert_equal(2, line('.')) + let v:statusmsg = '' + ta func2 + call assert_match('E435:', v:statusmsg) + call assert_equal(4, line('.')) + let v:statusmsg = '' + ta func3 + call assert_match('E435:', v:statusmsg) + call assert_equal(5, line('.')) + call assert_fails('ta func4', 'E434:') + + call delete('Xtags') + call delete('Xfoo') + set tags& +endfunc + +" Test for an unsorted tags file +func Test_tag_sort() + call writefile([ + \ "first\tXfoo\t1", + \ "ten\tXfoo\t3", + \ "six\tXfoo\t2"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int six() {} + int ten() {} + [CODE] + call writefile(code, 'Xfoo') + + call assert_fails('tag first', 'E432:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for an unsorted tags file +func Test_tag_fold() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "first\tXfoo\t1", + \ "second\tXfoo\t2", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + tag second + call assert_equal('Xfoo', bufname('')) + call assert_equal(2, line('.')) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for the :ltag command +func Test_ltag() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + call setloclist(0, [], 'f') + ltag third + call assert_equal('Xfoo', bufname('')) + call assert_equal(3, line('.')) + call assert_equal([{'lnum': 3, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', + \ 'module': '', 'text': 'third'}], getloclist(0)) + + ltag second + call assert_equal(2, line('.')) + call assert_equal([{'lnum': 0, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '^\Vint second() {}\$', 'valid': 1, 'vcol': 0, 'nr': 0, + \ 'type': '', 'module': '', 'text': 'second'}], getloclist(0)) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for setting the last search pattern to the tag search pattern +" when cpoptions has 't' +func Test_tag_last_search_pat() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}/", + \ "second\tXfoo\t/^int second() {}/", + \ "third\tXfoo\t/^int third() {}/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + let save_cpo = &cpo + set cpo+=t + let @/ = '' + tag second + call assert_equal('^int second() {}', @/) + let &cpo = save_cpo + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for jumping to a tag when the tag stack is full +func Test_tag_stack_full() + let l = [] + for i in range(10, 31) + let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] + endfor + call writefile(l, 'Xtags') + set tags=Xtags + + let l = [] + for i in range(10, 31) + let l += ["int var" .. i .. ";"] + endfor + call writefile(l, 'Xfoo') + + enew + for i in range(10, 30) + exe "tag var" .. i + endfor + let l = gettagstack() + call assert_equal(20, l.length) + call assert_equal('var11', l.items[0].tagname) + tag var31 + let l = gettagstack() + call assert_equal('var12', l.items[0].tagname) + call assert_equal('var31', l.items[19].tagname) + + " Jump from the top of the stack + call assert_fails('tag', 'E556:') + + " Pop from an unsaved buffer + enew! + call append(1, "sample text") + call assert_fails('pop', 'E37:') + call assert_equal(21, gettagstack().curidx) + enew! + + " Pop all the entries in the tag stack + call assert_fails('30pop', 'E555:') + + " Pop the tag stack when it is empty + call settagstack(1, {'items' : []}) + call assert_fails('pop', 'E73:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for browsing multiple matching tags +func Test_tag_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "first\tXfoo\t2", + \ "first\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int first() {} + int first() {} + [CODE] + call writefile(code, 'Xfoo') + + tag first + tlast + call assert_equal(3, line('.')) + call assert_fails('tnext', 'E428:') + tfirst + call assert_equal(1, line('.')) + call assert_fails('tprev', 'E425:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + " Test for the 'taglength' option func Test_tag_length() set tags=Xtags diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index d4ff42fd68..e830813081 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -126,3 +126,99 @@ func Test_tagsfile_without_trailing_newline() call delete('Xtags') set tags& endfunc + +" Test for ignoring comments in a tags file +func Test_tagfile_ignore_comments() + call writefile([ + \ "!_TAG_PROGRAM_NAME /Test tags generator/", + \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf", + \ "!_TAG_FILE_FORMAT 2 /extended format/", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal(1, len(l)) + call assert_equal('FBar', l[0].name) + + set tags& + call delete('Xtags') +endfunc + +" Test for using an excmd in a tags file to position the cursor (instead of a +" search pattern or a line number) +func Test_tagfile_excmd() + call writefile([ + \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : 'call cursor(3, 4)', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for duplicate fields in a tag in a tags file +func Test_duplicate_field() + call writefile([ + \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '4', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'typename' : 'int', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for tag address with ; +func Test_tag_addr_with_semicolon() + call writefile([ + \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf" + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '6;/^Func1/', + \ 'static' : 0, + \ 'name' : 'Func1', + \ 'kind' : 'f', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for format error in a tags file +func Test_format_error() + call writefile(['vFoo-Xfoo-4'], 'Xtags') + set tags=Xtags + + let caught_exception = v:false + try + let l = taglist('.*') + catch /E431:/ + " test succeeded + let caught_exception = v:true + catch + call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') +endfunc diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 4af52b536c..29f0433954 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -891,6 +891,14 @@ func Test_mps() bwipe! endfunc +func Test_empty_matchpairs() + split + set matchpairs= showmatch + call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")') + set matchpairs& noshowmatch + bwipe! +endfunc + " Test for ra on multi-byte characters func Test_ra_multibyte() new diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 13971a918d..ceaa5de92b 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -317,8 +317,8 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). func Test_nocatch_garbage_collect() - " skipped: Nvim does not support test_garbagecollect_soon(), test_override() - return + CheckFunction test_garbagecollect_soon + CheckFunction test_override " 'uptimetime. must be bigger than the timer timeout set ut=200 call test_garbagecollect_soon() diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 3b66071d6d..54caed3983 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,6 +3,8 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. +source check.vim + func Test_undotree() new @@ -135,7 +137,7 @@ func BackOne(expected) endfunc func Test_undo_del_chars() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime " Setup a buffer without creating undo entries new @@ -330,7 +332,7 @@ func Test_insert_expr() endfunc func Test_undofile_earlier() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime let t0 = localtime() - 43200 call test_settime(t0) diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index 67701ee3ca..e9e181ce3d 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -95,6 +95,59 @@ func Test_user_func() enew! endfunc +func Log(val, base = 10) + return log(a:val) / log(a:base) +endfunc + +func Args(mandatory, optional = v:null, ...) + return deepcopy(a:) +endfunc + +func Args2(a = 1, b = 2, c = 3) + return deepcopy(a:) +endfunc + +func MakeBadFunc() + func s:fcn(a, b=1, c) + endfunc +endfunc + +func Test_default_arg() + call assert_equal(1.0, Log(10)) + call assert_equal(log(10), Log(10, exp(1))) + call assert_fails("call Log(1,2,3)", 'E118') + + let res = Args(1) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, v:null) + call assert_equal(res['0'], 0) + + let res = Args(1,2) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, 2) + call assert_equal(res['0'], 0) + + let res = Args(1,2,3) + call assert_equal(res.mandatory, 1) + call assert_equal(res.optional, 2) + call assert_equal(res['0'], 1) + + call assert_fails("call MakeBadFunc()", 'E989') + call assert_fails("fu F(a=1 ,) | endf", 'E475') + + " Since neovim does not have v:none, the ability to use the default + " argument with the intermediate argument set to v:none has been omitted. + " Therefore, this test is not performed. + " let d = Args2(7, v:none, 9) + " call assert_equal([7, 2, 9], [d.a, d.b, d.c]) + + call assert_equal("\n" + \ .. " function Args2(a = 1, b = 2, c = 3)\n" + \ .. "1 return deepcopy(a:)\n" + \ .. " endfunction", + \ execute('func Args2')) +endfunc + func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index e8161f8fcb..c51fb3a759 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -84,7 +84,7 @@ func Test_list2str_str2list_utf8() " Null list is the same as an empty list call assert_equal('', list2str([])) - " call assert_equal('', list2str(test_null_list())) + call assert_equal('', list2str(v:_null_list)) endfunc func Test_list2str_str2list_latin1() diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim new file mode 100644 index 0000000000..2fbf130345 --- /dev/null +++ b/src/nvim/testdir/test_vartabs.vim @@ -0,0 +1,381 @@ +" Test for variable tabstops + +if !has("vartabs") + finish +endif + +source view_util.vim + +func s:compare_lines(expect, actual) + call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) +endfunc + +func Test_vartabs() + new + %d + + " Test normal operation of tabstops ... + set ts=4 + call setline(1, join(split('aaaaa', '\zs'), "\t")) + retab 8 + let expect = "a a\<tab>a a\<tab>a" + call assert_equal(expect, getline(1)) + + " ... and softtabstops + set ts=8 sts=6 + exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b" + let expect = "b b\<tab> b\<tab> b\<tab>b" + call assert_equal(expect, getline(1)) + + " Test variable tabstops. + set sts=0 vts=4,8,4,8 + exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c" + retab 8 + let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c" + call assert_equal(expect, getline(1)) + + set et vts=4,8,4,8 + exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d" + let expect = "d d d d d d" + call assert_equal(expect, getline(1)) + + " Changing ts should have no effect if vts is in use. + call cursor(1, 1) + set ts=6 + exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e" + let expect = "e e e e e e" + call assert_equal(expect, getline(1)) + + " Clearing vts should revert to using ts. + set vts= + exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f" + let expect = "f f f f f f" + call assert_equal(expect, getline(1)) + + " Test variable softtabstops. + set noet ts=8 vsts=12,2,6 + exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g" + let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g" + call assert_equal(expect, getline(1)) + + " Variable tabstops and softtabstops combined. + set vsts=6,12,8 vts=4,6,8 + exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h" + let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h" + call assert_equal(expect, getline(1)) + + " Retab with a single value, not using vts. + set ts=8 sts=0 vts= vsts= + exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i" + retab 4 + let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i" + call assert_equal(expect, getline(1)) + + " Retab with a single value, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j" + retab 4 + let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, not using vts. + set ts=6 sts=0 vts= vsts= + exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k" + retab 4,8 + let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l" + retab 4,8 + let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l" + call assert_equal(expect, getline(1)) + + " Check that global and local values are set. + set ts=4 vts=6 sts=8 vsts=10 + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that local values only are set. + setlocal ts=5 vts=7 sts=9 vsts=11 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that global values only are set. + setglobal ts=6 vts=8 sts=10 vsts=12 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 6) + call assert_equal(&vts, '8') + call assert_equal(&sts, 10) + call assert_equal(&vsts, '12') + bwipeout! + + set ts& vts& sts& vsts& et& + bwipeout! +endfunc + +func! Test_vartabs_breakindent() + if !exists("+breakindent") + return + endif + new + %d + + " Test normal operation of tabstops ... + set ts=4 + call setline(1, join(split('aaaaa', '\zs'), "\t")) + retab 8 + let expect = "a a\<tab>a a\<tab>a" + call assert_equal(expect, getline(1)) + + " ... and softtabstops + set ts=8 sts=6 + exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b" + let expect = "b b\<tab> b\<tab> b\<tab>b" + call assert_equal(expect, getline(1)) + + " Test variable tabstops. + set sts=0 vts=4,8,4,8 + exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c" + retab 8 + let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c" + call assert_equal(expect, getline(1)) + + set et vts=4,8,4,8 + exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d" + let expect = "d d d d d d" + call assert_equal(expect, getline(1)) + + " Changing ts should have no effect if vts is in use. + call cursor(1, 1) + set ts=6 + exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e" + let expect = "e e e e e e" + call assert_equal(expect, getline(1)) + + " Clearing vts should revert to using ts. + set vts= + exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f" + let expect = "f f f f f f" + call assert_equal(expect, getline(1)) + + " Test variable softtabstops. + set noet ts=8 vsts=12,2,6 + exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g" + let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g" + call assert_equal(expect, getline(1)) + + " Variable tabstops and softtabstops combined. + set vsts=6,12,8 vts=4,6,8 + exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h" + let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h" + call assert_equal(expect, getline(1)) + + " Retab with a single value, not using vts. + set ts=8 sts=0 vts= vsts= + exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i" + retab 4 + let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i" + call assert_equal(expect, getline(1)) + + " Retab with a single value, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j" + retab 4 + let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, not using vts. + set ts=6 sts=0 vts= vsts= + exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k" + retab 4,8 + let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l" + retab 4,8 + let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l" + call assert_equal(expect, getline(1)) + + " Check that global and local values are set. + set ts=4 vts=6 sts=8 vsts=10 + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that local values only are set. + setlocal ts=5 vts=7 sts=9 vsts=11 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that global values only are set. + setglobal ts=6 vts=8 sts=10 vsts=12 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 6) + call assert_equal(&vts, '8') + call assert_equal(&sts, 10) + call assert_equal(&vsts, '12') + bwipeout! + + bwipeout! +endfunc + +func Test_vartabs_linebreak() + if winwidth(0) < 40 + return + endif + new + 40vnew + %d + setl linebreak vartabstop=10,20,30,40 + call setline(1, "\tx\tx\tx\tx") + + let expect = [' x ', + \ 'x x ', + \ 'x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect, lines) + setl list listchars=tab:>- + let expect = ['>---------x>------------------ ', + \ 'x>------------------x>------------------', + \ 'x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect, lines) + setl linebreak vartabstop=40 + let expect = ['>---------------------------------------', + \ 'x>--------------------------------------', + \ 'x>--------------------------------------', + \ 'x>--------------------------------------', + \ 'x '] + let lines = ScreenLines([1, 5], winwidth(0)) + call s:compare_lines(expect, lines) + + " cleanup + bw! + bw! + set nolist listchars&vim +endfunc + +func Test_vartabs_shiftwidth() + "return + if winwidth(0) < 40 + return + endif + new + 40vnew + %d +" setl varsofttabstop=10,20,30,40 + setl shiftwidth=0 vartabstop=10,20,30,40 + call setline(1, "x") + + " Check without any change. + let expect = ['x '] + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect, lines) + " Test 1: + " shiftwidth depends on the indent, first check with cursor at the end of the + " line (which is the same as the start of the line, since there is only one + " character). + norm! $>> + let expect1 = [' x '] + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect1, lines) + call assert_equal(10, shiftwidth()) + call assert_equal(10, shiftwidth(1)) + call assert_equal(20, shiftwidth(virtcol('.'))) + norm! $>> + let expect2 = [' x ', '~ '] + let lines = ScreenLines([1, 2], winwidth(0)) + call s:compare_lines(expect2, lines) + call assert_equal(20, shiftwidth(virtcol('.')-2)) + call assert_equal(30, shiftwidth(virtcol('.'))) + norm! $>> + let expect3 = [' ', ' x ', '~ '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect3, lines) + call assert_equal(30, shiftwidth(virtcol('.')-2)) + call assert_equal(40, shiftwidth(virtcol('.'))) + norm! $>> + let expect4 = [' ', ' ', ' x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call assert_equal(40, shiftwidth(virtcol('.'))) + call s:compare_lines(expect4, lines) + + " Test 2: Put the cursor at the first column, result should be the same + call setline(1, "x") + norm! 0>> + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect1, lines) + norm! 0>> + let lines = ScreenLines([1, 2], winwidth(0)) + call s:compare_lines(expect2, lines) + norm! 0>> + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect3, lines) + norm! 0>> + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect4, lines) + + " cleanup + bw! + bw! +endfunc + +func Test_vartabs_failures() + call assert_fails('set vts=8,') + call assert_fails('set vsts=8,') + call assert_fails('set vts=8,,8') + call assert_fails('set vsts=8,,8') + call assert_fails('set vts=8,,8,') + call assert_fails('set vsts=8,,8,') + call assert_fails('set vts=,8') + call assert_fails('set vsts=,8') +endfunc + +func Test_vartabs_reset() + set vts=8 + set all& + call assert_equal('', &vts) +endfunc diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim index 46cf34979f..5fd38f7cdc 100644 --- a/src/nvim/testdir/test_version.vim +++ b/src/nvim/testdir/test_version.vim @@ -1,5 +1,8 @@ " Test :version Ex command +so check.vim +so shared.vim + func Test_version() " version should always return the same string. let v1 = execute('version') @@ -9,4 +12,15 @@ func Test_version() call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1) endfunc +func Test_version_redirect() + CheckNotGui + CheckCanRunGui + CheckUnix + + call RunVim([], [], '--clean -g --version >Xversion 2>&1') + call assert_match('Features included', readfile('Xversion')->join()) + + call delete('Xversion') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 7f50894f66..73c7960579 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -255,7 +255,6 @@ func TriggerTheProblem() endfunc func Test_visual_mode_reset() - set belloff=all enew let g:msg = "Everything's fine." enew @@ -268,7 +267,6 @@ func Test_visual_mode_reset() exe "normal! GV:call TriggerTheProblem()\<CR>" call assert_equal("Everything's fine.", g:msg) - set belloff& endfunc func Test_Visual_word_textobject() diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 687b1cb989..a522705238 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 @@ -550,16 +550,29 @@ endfunc func Test_winrestcmd() 2split 3vsplit - let a = winrestcmd() + let restcmd = winrestcmd() call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) wincmd = call assert_notequal(2, winheight(0)) call assert_notequal(3, winwidth(0)) - exe a + exe restcmd call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) only + + wincmd v + wincmd s + wincmd v + redraw + let restcmd = winrestcmd() + wincmd _ + wincmd | + exe restcmd + redraw + call assert_equal(restcmd, winrestcmd()) + + only endfunc function! Fun_RenewFile() @@ -808,6 +821,55 @@ func Test_winnr() only | tabonly endfunc +func Test_win_splitmove() + edit a + leftabove split b + leftabove vsplit c + leftabove split d + call assert_equal(0, win_splitmove(winnr(), winnr('l'))) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'd') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'd') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'd') + call assert_equal(bufname(winbufnr(2)), 'c') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'a') + call assert_equal(bufname(winbufnr(4)), 'd') + only | bd + + call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') + call assert_fails('call win_splitmove(123, winnr())', 'E957:') + call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:') + + tabnew + call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:') + tabclose +endfunc + +func Test_floatwin_splitmove() + vsplit + let win2 = win_getid() + let popup_winid = nvim_open_win(0, 0, {'relative': 'win', + \ 'row': 3, 'col': 3, 'width': 12, 'height': 3}) + call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') + call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:') + + call nvim_win_close(popup_winid, 1) + bwipe +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 56031662a3..c62c01d5f3 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -1,5 +1,8 @@ " Tests for the writefile() function and some :write commands. +source check.vim +source term_util.vim + func Test_writefile() let f = tempname() call writefile(["over","written"], f, "b") @@ -179,3 +182,120 @@ func Test_writefile_sync_arg() call writefile(['two'], 'Xtest', 'S') call delete('Xtest') endfunc + +" Tests for reading and writing files with conversion for Win32. +func Test_write_file_encoding() + CheckMSWindows + throw 'skipped: Nvim does not support :w ++enc=cp1251' + let save_encoding = &encoding + let save_fileencodings = &fileencodings + set encoding& fileencodings& + let text =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call writefile(text, 'Xfile') + edit Xfile + + " write tests: + " combine three values for 'encoding' with three values for 'fileencoding' + " also write files for read tests + call cursor(1, 1) + set encoding=utf-8 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=utf-8 Xutf8 + let expected =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call cursor(2, 1) + set encoding=cp1251 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=cp1251 Xcp1251 + let expected =<< trim END + 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call cursor(3, 1) + set encoding=cp866 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=cp866 Xcp866 + let expected =<< trim END + 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with utf-8 'encoding' + set encoding=utf-8 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=utf-8 Xtest + e Xcp1251 + .w ++enc=utf-8 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=utf-8 >> Xtest + let expected =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with cp1251 'encoding' + set encoding=utf-8 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=cp1251 Xtest + e Xcp1251 + .w ++enc=cp1251 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=cp1251 >> Xtest + let expected =<< trim END + 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with cp866 'encoding' + set encoding=cp866 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=cp866 Xtest + e Xcp1251 + .w ++enc=cp866 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=cp866 >> Xtest + let expected =<< trim END + 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call delete('Xfile') + call delete('Xtest') + call delete('Xutf8') + call delete('Xcp1251') + call delete('Xcp866') + let &encoding = save_encoding + let &fileencodings = save_fileencodings + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 124f96e039..6705ab98c2 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); @@ -557,8 +557,8 @@ HandleState ut_handle_background_color(TermInput *input) static void handle_raw_buffer(TermInput *input, bool force) { - HandleState is_paste; - HandleState is_bc; + HandleState is_paste = kNotApplicable; + HandleState is_bc = kNotApplicable; do { if (!force diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 62d7dc8b18..ed40a64c66 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -55,7 +55,11 @@ #define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) #define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ - ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) +#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ + ((is_screen) \ + ? DCS_STR seq STERM_STR : (is_tmux) \ + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -297,6 +301,12 @@ static void terminfo_start(UI *ui) data->invis, sizeof data->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(data->ut, unibi_max_colors); + // Ask the terminal to send us the background color. + // If get_bg is sent at the same time after enter_ca_mode, tmux will not send + // get_bg to the host terminal. To avoid this, send get_bg before + // enter_ca_mode. + data->input.waiting_for_bg_response = 5; + unibi_out_ext(ui, data->unibi_ext.get_bg); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 unibi_out(ui, unibi_enter_ca_mode); @@ -304,9 +314,6 @@ static void terminfo_start(UI *ui) unibi_out_ext(ui, data->unibi_ext.save_title); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); - // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -328,6 +335,7 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } + flush_buf(ui); } @@ -1772,8 +1780,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + data->unibi_ext.get_bg = + (int)unibi_add_ext_str(ut, "ext.get_bg", + SCREEN_TMUX_WRAP((screen && !tmux), tmux, + "\x1b]11;?\x07")); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { diff --git a/src/nvim/ui.c b/src/nvim/ui.c index c6c09c80d7..94b6e9e39d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -110,6 +110,7 @@ static char uilog_last_event[1024] = { 0 }; void ui_init(void) { default_grid.handle = 1; + msg_grid_adj.target = &default_grid; ui_comp_init(); } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 9d3ec21949..a2e9266fbb 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -127,6 +127,9 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid, bool on_top) { bool moved; + + grid->comp_height = height; + grid->comp_width = width; if (grid->comp_index != 0) { moved = (row != grid->comp_row) || (col != grid->comp_col); if (ui_comp_should_draw()) { @@ -157,7 +160,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 @@ -181,14 +184,12 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, insert_at--; } // not found: new grid - kv_push(layers, grid); - if (insert_at < kv_size(layers)-1) { - for (size_t i = kv_size(layers)-1; i > insert_at; i--) { - kv_A(layers, i) = kv_A(layers, i-1); - kv_A(layers, i)->comp_index = i; - } - kv_A(layers, insert_at) = grid; + kv_pushp(layers); + for (size_t i = kv_size(layers)-1; i > insert_at; i--) { + kv_A(layers, i) = kv_A(layers, i-1); + kv_A(layers, i)->comp_index = i; } + kv_A(layers, insert_at) = grid; grid->comp_row = row; grid->comp_col = col; @@ -277,6 +278,9 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, // should configure all grids before entering win_update() if (curgrid != &default_grid) { size_t new_index = kv_size(layers)-1; + if (kv_A(layers, new_index) == &msg_grid) { + new_index--; + } if (kv_A(layers, new_index) == &pum_grid) { new_index--; } @@ -334,17 +338,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row] +(size_t)startcol]; + int grid_width, grid_height; while (col < endcol) { int until = 0; for (size_t i = 0; i < kv_size(layers); i++) { ScreenGrid *g = kv_A(layers, i); - if (g->comp_row > row || row >= g->comp_row + g->Rows + // compose_line may have been called after a shrinking operation but + // before the resize has actually been applied. Therefore, we need to + // first check to see if any grids have pending updates to width/height, + // to ensure that we don't accidentally put any characters into `linebuf` + // that have been invalidated. + grid_width = MIN(g->Columns, g->comp_width); + grid_height = MIN(g->Rows, g->comp_height); + if (g->comp_row > row || row >= g->comp_row + grid_height || g->comp_disabled) { continue; } - if (g->comp_col <= col && col < g->comp_col+g->Columns) { + if (g->comp_col <= col && col < g->comp_col + grid_width) { grid = g; - until = g->comp_col+g->Columns; + until = g->comp_col + grid_width; } else if (g->comp_col > col) { until = MIN(until, g->comp_col); } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index da464c56dc..f52850f6f3 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -580,6 +580,10 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) uep->ue_array = NULL; uep->ue_next = curbuf->b_u_newhead->uh_entry; curbuf->b_u_newhead->uh_entry = uep; + if (reload) { + // buffer was reloaded, notify text change subscribers + curbuf->b_u_newhead->uh_flags |= UH_RELOAD; + } curbuf->b_u_synced = false; undo_undoes = false; @@ -590,13 +594,20 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) } -# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */ +// magic at start of undofile +# define UF_START_MAGIC "Vim\237UnDo\345" # define UF_START_MAGIC_LEN 9 -# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ -# define UF_HEADER_END_MAGIC 0xe7aa /* magic after last header */ -# define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */ -# define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */ -# define UF_VERSION 2 /* 2-byte undofile version number */ +// magic at start of header +# define UF_HEADER_MAGIC 0x5fd0 +// magic after last header +# define UF_HEADER_END_MAGIC 0xe7aa +// magic at start of entry +# define UF_ENTRY_MAGIC 0xf518 +// magic after last entry +# define UF_ENTRY_END_MAGIC 0x3581 + +// 2-byte undofile version number +# define UF_VERSION 3 /* extra fields for header */ # define UF_LAST_SAVE_NR 1 @@ -843,6 +854,15 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) } } undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + + // Write all extmark undo objects + for (size_t i = 0; i < kv_size(uhp->uh_extmark); i++) { + if (!serialize_extmark(bi, kv_A(uhp->uh_extmark, i))) { + return false; + } + } + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + return true; } @@ -924,9 +944,95 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, return NULL; } + // Unserialize all extmark undo information + ExtmarkUndoObject *extup; + kv_init(uhp->uh_extmark); + + while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC) { + bool error = false; + extup = unserialize_extmark(bi, &error, file_name); + if (error) { + kv_destroy(uhp->uh_extmark); + xfree(extup); + return NULL; + } + kv_push(uhp->uh_extmark, *extup); + xfree(extup); + } + if (c != UF_ENTRY_END_MAGIC) { + corruption_error("entry end", file_name); + u_free_uhp(uhp); + return NULL; + } + return uhp; } +static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup) +{ + if (extup.type == kExtmarkSplice) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.splice), + sizeof(ExtmarkSplice))) { + return false; + } + } else if (extup.type == kExtmarkMove) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.move), sizeof(ExtmarkMove))) { + return false; + } + } + // Note: We do not serialize ExtmarkSavePos information, since + // buffer marktrees are not retained when closing/reopening a file + return true; +} + +static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, + const char *filename) +{ + UndoObjectType type; + uint8_t *buf = NULL; + size_t n_elems; + + ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject)); + + type = (UndoObjectType)undo_read_4c(bi); + extup->type = type; + if (type == kExtmarkSplice) { + n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.splice = *(ExtmarkSplice *)buf; + } else if (type == kExtmarkMove) { + n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.move = *(ExtmarkMove *)buf; + } else { + goto error; + } + + if (buf) { + xfree(buf); + } + + return extup; + +error: + xfree(extup); + if (buf) { + xfree(buf); + } + *error = true; + return NULL; +} + /// Serializes "uep". /// /// @param bi The buffer information @@ -2157,8 +2263,9 @@ static void u_undoredo(int undo, bool do_buf_event) u_check(FALSE); #endif old_flags = curhead->uh_flags; - new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + - ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); + new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + | ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0) + | (old_flags & UH_RELOAD); setpcmark(); /* @@ -2299,6 +2406,11 @@ static void u_undoredo(int undo, bool do_buf_event) extmark_apply_undo(undo_info, undo); } } + if (curhead->uh_flags & UH_RELOAD) { + // TODO(bfredl): this is a bit crude. When 'undoreload' is used we + // should have all info to send a buffer-reloaing on_lines/on_bytes event + buf_updates_unload(curbuf, true); + } // finish Adjusting extmarks diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index cc2c39a711..b46295a15d 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -69,9 +69,10 @@ struct u_header { #endif }; -/* values for uh_flags */ -#define UH_CHANGED 0x01 /* b_changed flag before undo/after redo */ -#define UH_EMPTYBUF 0x02 /* buffer was empty */ +// values for uh_flags +#define UH_CHANGED 0x01 // b_changed flag before undo/after redo +#define UH_EMPTYBUF 0x02 // buffer was empty +#define UH_RELOAD 0x04 // buffer was reloaded /// Structure passed around between undofile functions. typedef struct { 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..0245c472ef 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -158,7 +158,9 @@ enum { EXPAND_MESSAGES, EXPAND_MAPCLEAR, EXPAND_ARGLIST, + EXPAND_DIFF_BUFFERS, 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/src/nvim/window.c b/src/nvim/window.c index 00f49724b6..c482d265ff 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -605,6 +605,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_vsep_width = 0; win_config_float(wp, fconfig); + win_set_inner_size(wp); wp->w_pos_changed = true; redraw_later(wp, VALID); return wp; @@ -667,6 +668,12 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } bool change_external = fconfig.external != wp->w_float_config.external; + bool change_border = (fconfig.border != wp->w_float_config.border + || memcmp(fconfig.border_hl_ids, + wp->w_float_config.border_hl_ids, + sizeof fconfig.border_hl_ids)); + + wp->w_float_config = fconfig; if (!ui_has(kUIMultigrid)) { @@ -676,11 +683,18 @@ void win_config_float(win_T *wp, FloatConfig fconfig) win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); + wp->w_pos_changed = true; - if (change_external) { + if (change_external || change_border) { wp->w_hl_needs_update = true; redraw_later(wp, NOT_VALID); } + + // changing border style while keeping border only requires redrawing border + if (fconfig.border) { + wp->w_redr_border = true; + redraw_later(wp, VALID); + } } void win_check_anchored_floats(win_T *win) @@ -710,10 +724,10 @@ int win_fdccol_count(win_T *wp) } -static void ui_ext_win_position(win_T *wp) +void ui_ext_win_position(win_T *wp) { if (!wp->w_floating) { - ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow, + ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow, wp->w_wincol, wp->w_width, wp->w_height); return; } @@ -743,8 +757,8 @@ static void ui_ext_win_position(win_T *wp) } if (ui_has(kUIMultigrid)) { String anchor = cstr_to_string(float_anchor_str[c.anchor]); - ui_call_win_float_pos(wp->w_grid.handle, wp->handle, anchor, grid->handle, - row, col, c.focusable); + ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, + grid->handle, row, col, c.focusable); } else { // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. @@ -759,17 +773,17 @@ static void ui_ext_win_position(win_T *wp) wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); bool on_top = (curwin == wp) || !curwin->w_floating; - ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, - wp->w_width, valid, on_top); - ui_check_cursor_grid(wp->w_grid.handle); - wp->w_grid.focusable = wp->w_float_config.focusable; + ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, + wp->w_height_outer, wp->w_width_outer, valid, on_top); + ui_check_cursor_grid(wp->w_grid_alloc.handle); + wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; redraw_later(wp, NOT_VALID); } } } else { - ui_call_win_external_pos(wp->w_grid.handle, wp->handle); + ui_call_win_external_pos(wp->w_grid_alloc.handle, wp->handle); } } @@ -784,260 +798,12 @@ void ui_ext_win_viewport(win_T *wp) // interact with incomplete final line? Diff filler lines? botline = wp->w_buffer->b_ml.ml_line_count; } - ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1, + ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline-1, botline, wp->w_cursor.lnum-1, wp->w_cursor.col); wp->w_viewport_invalid = false; } } -static bool parse_float_anchor(String anchor, FloatAnchor *out) -{ - if (anchor.size == 0) { - *out = (FloatAnchor)0; - } - char *str = anchor.data; - if (striequal(str, "NW")) { - *out = 0; // NW is the default - } else if (striequal(str, "NE")) { - *out = kFloatAnchorEast; - } else if (striequal(str, "SW")) { - *out = kFloatAnchorSouth; - } else if (striequal(str, "SE")) { - *out = kFloatAnchorSouth | kFloatAnchorEast; - } else { - return false; - } - return true; -} - -static bool parse_float_relative(String relative, FloatRelative *out) -{ - char *str = relative.data; - if (striequal(str, "editor")) { - *out = kFloatRelativeEditor; - } else if (striequal(str, "win")) { - *out = kFloatRelativeWindow; - } else if (striequal(str, "cursor")) { - *out = kFloatRelativeCursor; - } else { - return false; - } - return true; -} - -static bool parse_float_bufpos(Array bufpos, lpos_T *out) -{ - if (bufpos.size != 2 - || bufpos.items[0].type != kObjectTypeInteger - || bufpos.items[1].type != kObjectTypeInteger) { - return false; - } - out->lnum = bufpos.items[0].data.integer; - out->col = bufpos.items[1].data.integer; - return true; -} - -bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, - Error *err) -{ - // TODO(bfredl): use a get/has_key interface instead and get rid of extra - // flags - bool has_row = false, has_col = false, has_relative = false; - bool has_external = false, has_window = false; - bool has_width = false, has_height = false; - bool has_bufpos = false; - - for (size_t i = 0; i < config.size; i++) { - char *key = config.items[i].key.data; - Object val = config.items[i].value; - if (!strcmp(key, "row")) { - has_row = true; - if (val.type == kObjectTypeInteger) { - fconfig->row = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->row = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'row' key must be Integer or Float"); - return false; - } - } else if (!strcmp(key, "col")) { - has_col = true; - if (val.type == kObjectTypeInteger) { - fconfig->col = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->col = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'col' key must be Integer or Float"); - return false; - } - } else if (strequal(key, "width")) { - has_width = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->width = val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'width' key must be a positive Integer"); - return false; - } - } else if (strequal(key, "height")) { - has_height = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->height= val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'height' key must be a positive Integer"); - return false; - } - } else if (!strcmp(key, "anchor")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'anchor' key must be String"); - return false; - } - if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'anchor' key"); - return false; - } - } else if (!strcmp(key, "relative")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'relative' key must be String"); - return false; - } - // ignore empty string, to match nvim_win_get_config - if (val.data.string.size > 0) { - has_relative = true; - if (!parse_float_relative(val.data.string, &fconfig->relative)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'relative' key"); - return false; - } - } - } else if (!strcmp(key, "win")) { - has_window = true; - if (val.type != kObjectTypeInteger - && val.type != kObjectTypeWindow) { - api_set_error(err, kErrorTypeValidation, - "'win' key must be Integer or Window"); - return false; - } - fconfig->window = val.data.integer; - } else if (!strcmp(key, "bufpos")) { - if (val.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "'bufpos' key must be Array"); - return false; - } - if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'bufpos' key"); - return false; - } - has_bufpos = true; - } else if (!strcmp(key, "external")) { - if (val.type == kObjectTypeInteger) { - fconfig->external = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->external = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'external' key must be Boolean"); - return false; - } - has_external = fconfig->external; - } else if (!strcmp(key, "focusable")) { - if (val.type == kObjectTypeInteger) { - fconfig->focusable = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->focusable = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'focusable' key must be Boolean"); - return false; - } - } else if (!strcmp(key, "style")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'style' key must be String"); - return false; - } - if (val.data.string.data[0] == NUL) { - fconfig->style = kWinStyleUnused; - } else if (striequal(val.data.string.data, "minimal")) { - fconfig->style = kWinStyleMinimal; - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'style' key"); - } - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid key '%s'", key); - return false; - } - } - - if (has_window && !(has_relative - && fconfig->relative == kFloatRelativeWindow)) { - api_set_error(err, kErrorTypeValidation, - "'win' key is only valid with relative='win'"); - return false; - } - - if ((has_relative && fconfig->relative == kFloatRelativeWindow) - && (!has_window || fconfig->window == 0)) { - fconfig->window = curwin->handle; - } - - if (has_window && !has_bufpos) { - fconfig->bufpos.lnum = -1; - } - - if (has_bufpos) { - if (!has_row) { - fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; - has_row = true; - } - if (!has_col) { - fconfig->col = 0; - has_col = true; - } - } - - if (has_relative && has_external) { - api_set_error(err, kErrorTypeValidation, - "Only one of 'relative' and 'external' must be used"); - return false; - } else if (!reconf && !has_relative && !has_external) { - api_set_error(err, kErrorTypeValidation, - "One of 'relative' and 'external' must be used"); - return false; - } else if (has_relative) { - fconfig->external = false; - } - - if (!reconf && !(has_height && has_width)) { - api_set_error(err, kErrorTypeValidation, - "Must specify 'width' and 'height'"); - return false; - } - - if (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); - return false; - } - - if (has_relative != has_row || has_row != has_col) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); - return false; - } - return true; -} - /* * split the current window, implements CTRL-W s and :split * @@ -1539,6 +1305,10 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) p_wh = i; } + if (!win_valid(oldwin)) { + return FAIL; + } + // Send the window positions to the UI oldwin->w_pos_changed = true; @@ -1619,6 +1389,23 @@ static void win_init_some(win_T *newp, win_T *oldp) win_copy_options(oldp, newp); } +/// Return TRUE if "win" is floating window in the current tab page. +/// +/// @param win window to check +bool win_valid_floating(const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (win == NULL) { + return false; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp == win) { + return wp->w_floating; + } + } + return false; +} /// Check if "win" is a pointer to an existing window in the current tabpage. /// @@ -1936,12 +1723,12 @@ static void win_totop(int size, int flags) } if (curwin->w_floating) { - ui_comp_remove_grid(&curwin->w_grid); + ui_comp_remove_grid(&curwin->w_grid_alloc); if (ui_has(kUIMultigrid)) { curwin->w_pos_changed = true; } else { // No longer a float, a non-multigrid UI shouldn't draw it as such - ui_call_win_hide(curwin->w_grid.handle); + ui_call_win_hide(curwin->w_grid_alloc.handle); win_free_grid(curwin, false); } } else { @@ -2564,11 +2351,11 @@ int win_close(win_T *win, bool free_buf) bool was_floating = win->w_floating; if (ui_has(kUIMultigrid)) { - ui_call_win_close(win->w_grid.handle); + ui_call_win_close(win->w_grid_alloc.handle); } if (win->w_floating) { - ui_comp_remove_grid(&win->w_grid); + ui_comp_remove_grid(&win->w_grid_alloc); if (win->w_float_config.external) { for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { if (tp == curtab) { @@ -3746,9 +3533,11 @@ void win_init_size(void) { firstwin->w_height = ROWS_AVAIL; firstwin->w_height_inner = firstwin->w_height; + firstwin->w_height_outer = firstwin->w_height; topframe->fr_height = ROWS_AVAIL; firstwin->w_width = Columns; firstwin->w_width_inner = firstwin->w_width; + firstwin->w_width_outer = firstwin->w_width; topframe->fr_width = Columns; } @@ -4114,7 +3903,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) win_remove(wp, old_curtab); win_append(lastwin_nofloating(), wp); } else { - ui_comp_remove_grid(&wp->w_grid); + ui_comp_remove_grid(&wp->w_grid_alloc); } } wp->w_pos_changed = true; @@ -4709,7 +4498,7 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->handle = ++last_win_id; handle_register_window(new_wp); - grid_assign_handle(&new_wp->w_grid); + grid_assign_handle(&new_wp->w_grid_alloc); // Init w: variables. new_wp->w_vars = tv_dict_alloc(); @@ -4833,15 +4622,14 @@ win_free ( void win_free_grid(win_T *wp, bool reinit) { - if (wp->w_grid.handle != 0 && ui_has(kUIMultigrid)) { - ui_call_grid_destroy(wp->w_grid.handle); - wp->w_grid.handle = 0; + if (wp->w_grid_alloc.handle != 0 && ui_has(kUIMultigrid)) { + ui_call_grid_destroy(wp->w_grid_alloc.handle); } - grid_free(&wp->w_grid); + grid_free(&wp->w_grid_alloc); if (reinit) { // if a float is turned into a split and back into a float, the grid // data structure will be reused - memset(&wp->w_grid, 0, sizeof(wp->w_grid)); + memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); } } @@ -5502,7 +5290,7 @@ void win_setminheight(void) // loop until there is a 'winminheight' that is possible while (p_wmh > 0) { const int room = Rows - p_ch; - const int needed = frame_minheight(topframe, NULL); + const int needed = min_rows() - 1; // 1 was added for the cmdline if (room >= needed) { break; } @@ -5946,6 +5734,17 @@ void win_set_inner_size(win_T *wp) if (wp->w_buffer->terminal) { terminal_check_size(wp->w_buffer->terminal); } + + bool has_border = wp->w_floating && wp->w_float_config.border; + for (int i = 0; i < 4; i++) { + wp->w_border_adj[i] = + has_border && wp->w_float_config.border_chars[2 * i+1][0]; + } + + wp->w_height_outer = (wp->w_height_inner + + wp->w_border_adj[0] + wp->w_border_adj[2]); + wp->w_width_outer = (wp->w_width_inner + + wp->w_border_adj[1] + wp->w_border_adj[3]); } /// Set the width of a window. @@ -5960,9 +5759,17 @@ void win_new_width(win_T *wp, int width) void win_comp_scroll(win_T *wp) { + const long old_w_p_scr = wp->w_p_scr; + wp->w_p_scr = wp->w_height / 2; - if (wp->w_p_scr == 0) + if (wp->w_p_scr == 0) { wp->w_p_scr = 1; + } + if (wp->w_p_scr != old_w_p_scr) { + // Used by "verbose set scroll". + wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_sid = SID_WINLAYOUT; + wp->w_p_script_ctx[WV_SCROLL].script_ctx.sc_lnum = 0; + } } /* @@ -7074,11 +6881,11 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer) void win_ui_flush(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_pos_changed && wp->w_grid.chars != NULL) { + if (wp->w_pos_changed && wp->w_grid_alloc.chars != NULL) { if (tp == curtab) { ui_ext_win_position(wp); } else { - ui_call_win_hide(wp->w_grid.handle); + ui_call_win_hide(wp->w_grid_alloc.handle); } wp->w_pos_changed = false; } diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index fb8ed6a9d7..81fad206da 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -513,6 +513,28 @@ describe('api/buf', function() eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {})) eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {})) end) + + it("correctly marks changed region for redraw #13890", function() + local screen = Screen.new(20, 5) + screen:attach() + + insert([[ + AAA + BBB + ]]) + + curbufmeths.set_text(0, 0, 1, 3, {'XXX', 'YYY'}) + + screen:expect([[ + XXX | + YYY | + ^ | + ~ | + | + + ]]) + + end) end) describe('nvim_buf_get_offset', function() diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3ff3efb8c9..3db44f3f11 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -475,6 +475,18 @@ describe('API', function() end) end) + describe('nvim_notify', function() + it('can notify a info message', function() + nvim("notify", "hello world", 2, {}) + end) + + it('can be overriden', function() + command("lua vim.notify = function(...) return 42 end") + eq(42, meths.exec_lua("return vim.notify('Hello world')", {})) + nvim("notify", "hello world", 4, {}) + end) + end) + describe('nvim_input', function() it("VimL error: does NOT fail, updates v:errmsg", function() local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>") @@ -2075,4 +2087,67 @@ describe('API', function() eq("", meths.exec("messages", true)) end) end) + + + describe('nvim_open_term', function() + local screen + + before_each(function() + clear() + screen = Screen.new(100, 35) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.Plum1}; + [2] = {background = tonumber('0xffff40'), bg_indexed = true}; + [3] = {background = Screen.colors.Plum1, fg_indexed = true, foreground = tonumber('0x00e000')}; + [4] = {bold = true, reverse = true, background = Screen.colors.Plum1}; + }) + end) + + it('can batch process sequences', function() + local b = meths.create_buf(true,true) + meths.open_win(b, false, {width=79, height=31, row=1, col=1, relative='editor'}) + local t = meths.open_term(b, {}) + + meths.chan_send(t, io.open("test/functional/fixtures/smile2.cat", "r"):read("*a")) + screen:expect{grid=[[ + ^ | + {0:~}{1::smile }{0: }| + {0:~}{1: }{2:oooo$$$$$$$$$$$$oooo}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{0: }| + {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:o$}{1: }{2:$$}{1: }{2:o$}{1: }{0: }| + {0:~}{1: }{2:o}{1: }{2:$}{1: }{2:oo}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:$$}{1: }{2:$$}{1: }{2:$$o$}{1: }{0: }| + {0:~}{1: }{2:oo}{1: }{2:$}{1: }{2:$}{1: "}{2:$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$o}{1: }{2:$$$o$$o$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$$o$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$o}{1: }{2:$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$}{1: """}{2:$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$}{1:""""}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$}{1:" }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$o}{1: }{0: }| + {0:~}{1: }{2:$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" "}{2:$$$$$$ooooo$$$$o}{1: }{0: }| + {0:~}{1: }{2:o$$$oooo$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:o$$$$$$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$}{1:"}{2:$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$}{1:"""""""" }{0: }| + {0:~}{1: """" }{2:$$$$}{1: "}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" }{2:o$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$o}{1: """}{2:$$$$$$$$$$$$$$$$$$}{1:"}{2:$$}{1:" }{2:$$$}{1: }{0: }| + {0:~}{1: }{2:$$$o}{1: "}{2:$$}{1:""}{2:$$$$$$}{1:"""" }{2:o$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$o}{1: }{2:o$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$$o}{1: }{2:o$$$$$$o}{1:"}{2:$$$$o}{1: }{2:o$$$$}{1: }{0: }| + {0:~}{1: "}{2:$$$$$oo}{1: ""}{2:$$$$o$$$$$o}{1: }{2:o$$$$}{1:"" }{0: }| + {0:~}{1: ""}{2:$$$$$oooo}{1: "}{2:$$$o$$$$$$$$$}{1:""" }{0: }| + {0:~}{1: ""}{2:$$$$$$$oo}{1: }{2:$$$$$$$$$$}{1: }{0: }| + {0:~}{1: """"}{2:$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$$$}{1: }{0: }| + {0:~}{1: }{2:$$$$$$$$$$}{1:" }{0: }| + {0:~}{1: "}{2:$$$}{1:"""" }{0: }| + {0:~}{1: }{0: }| + {0:~}{3:Press ENTER or type command to continue}{1: }{0: }| + {0:~}{4:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{0: }| + {0:~}{1::call nvim__screenshot("smile2.cat") }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end) + end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 7471f50dbd..ceeb84cec9 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -347,4 +347,44 @@ describe('API/win', function() eq('', funcs.getcmdwintype()) end) end) + + describe('hide', function() + it('can hide current window', function() + local oldwin = meths.get_current_win() + command('split') + local newwin = meths.get_current_win() + meths.win_hide(newwin) + eq({oldwin}, meths.list_wins()) + end) + it('can hide noncurrent window', function() + local oldwin = meths.get_current_win() + command('split') + local newwin = meths.get_current_win() + meths.win_hide(oldwin) + eq({newwin}, meths.list_wins()) + end) + it('does not close the buffer', function() + local oldwin = meths.get_current_win() + local oldbuf = meths.get_current_buf() + local buf = meths.create_buf(true, false) + local newwin = meths.open_win(buf, true, { + relative='win', row=3, col=3, width=12, height=3 + }) + meths.win_hide(newwin) + eq({oldwin}, meths.list_wins()) + eq({oldbuf, buf}, meths.list_bufs()) + end) + it('deletes the buffer when bufhidden=wipe', function() + local oldwin = meths.get_current_win() + local oldbuf = meths.get_current_buf() + local buf = meths.create_buf(true, false) + local newwin = meths.open_win(buf, true, { + relative='win', row=3, col=3, width=12, height=3 + }) + meths.buf_set_option(buf, 'bufhidden', 'wipe') + meths.win_hide(newwin) + eq({oldwin}, meths.list_wins()) + eq({oldbuf}, meths.list_bufs()) + end) + end) end) diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua index 1d52e9948f..3f9a0ad09b 100644 --- a/test/functional/autoread/focus_spec.lua +++ b/test/functional/autoread/focus_spec.lua @@ -9,6 +9,7 @@ local feed_data = thelpers.feed_data if helpers.pending_win32(pending) then return end describe('autoread TUI FocusGained/FocusLost', function() + local f1 = 'xtest-foo' local screen before_each(function() @@ -17,8 +18,12 @@ describe('autoread TUI FocusGained/FocusLost', function() ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') end) + teardown(function() + os.remove(f1) + end) + it('external file change', function() - local path = 'xtest-foo' + local path = f1 local expected_addition = [[ line 1 line 2 diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 6d1182478a..9de0d08e79 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") @@ -946,8 +963,10 @@ describe('jobs', function() return rv end + local j local function send(str) - nvim('command', 'call jobsend(j, "'..str..'")') + -- check no nvim_chan_free double free with pty job (#14198) + meths.chan_send(j, str) end before_each(function() @@ -962,6 +981,7 @@ describe('jobs', function() nvim('command', 'let g:job_opts.pty = 1') nvim('command', 'let exec = [expand("<cfile>:p")]') nvim('command', "let j = jobstart(exec, g:job_opts)") + j = eval'j' eq('tty ready', next_chunk()) end) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index fa8f7d873f..d403fbc878 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -14,7 +14,8 @@ describe('NULL', function() clear() command('let L = v:_null_list') command('let D = v:_null_dict') - command('let S = $XXX_NONEXISTENT_VAR_XXX') + command('let S = v:_null_string') + command('let V = $XXX_NONEXISTENT_VAR_XXX') end) local tmpfname = 'Xtest-functional-viml-null' after_each(function() @@ -129,6 +130,7 @@ describe('NULL', function() null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, 0) null_test('is accepted by :cexpr', 'cexpr L', 0) null_test('is accepted by :lexpr', 'lexpr L', 0) + null_expr_test('does not crash execute()', 'execute(L)', 0, '') end) describe('dict', function() it('does not crash when indexing NULL dict', function() @@ -142,4 +144,26 @@ describe('NULL', function() null_expr_test('makes map() return v:_null_dict', 'map(D, "v:val") is# D', 0, 1) null_expr_test('makes filter() return v:_null_dict', 'filter(D, "1") is# D', 0, 1) end) + describe('string', function() + null_test('does not crash :echomsg', 'echomsg S', 0) + null_test('does not crash :execute', 'execute S', 0) + null_expr_test('does not crash execute()', 'execute(S)', 0, '') + null_expr_test('makes executable() error out', 'executable(S)', 'E928: String required', 0) + null_expr_test('does not crash filereadable()', 'filereadable(S)', 0, 0) + null_expr_test('does not crash filewritable()', 'filewritable(S)', 0, 0) + null_expr_test('does not crash fnamemodify()', 'fnamemodify(S, S)', 0, '') + null_expr_test('does not crash getfperm()', 'getfperm(S)', 0, '') + null_expr_test('does not crash getfsize()', 'getfsize(S)', 0, -1) + null_expr_test('does not crash getftime()', 'getftime(S)', 0, -1) + null_expr_test('does not crash getftype()', 'getftype(S)', 0, '') + null_expr_test('does not crash glob()', 'glob(S)', 0, '') + null_expr_test('does not crash globpath()', 'globpath(S, S)', 0, '') + null_expr_test('does not crash mkdir()', 'mkdir(S)', 0, 0) + null_expr_test('does not crash sort()', 'sort(["b", S, "a"])', 0, {'', 'a', 'b'}) + null_expr_test('does not crash split()', 'split(S)', 0, {}) + + null_test('can be used to set an option', 'let &grepprg = S', 0) + + null_expr_test('is equal to non-existent variable', 'S == V', 0, 1) + end) end) diff --git a/test/functional/eval/writefile_spec.lua b/test/functional/eval/writefile_spec.lua index 0bb7523d7e..356680ba7c 100644 --- a/test/functional/eval/writefile_spec.lua +++ b/test/functional/eval/writefile_spec.lua @@ -59,6 +59,16 @@ describe('writefile()', function() eq('\n', read_file(fname)) end) + it('writes list with a null string to a file', function() + eq(0, exc_exec( + ('call writefile([v:_null_string], "%s", "b")'):format( + fname))) + eq('', read_file(fname)) + eq(0, exc_exec(('call writefile([v:_null_string], "%s")'):format( + fname))) + eq('\n', read_file(fname)) + end) + it('appends to a file', function() eq(nil, read_file(fname)) eq(0, funcs.writefile({'abc', 'def', 'ghi'}, fname)) diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index 5c67431221..e5c9a20db3 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -371,4 +371,79 @@ describe('VimL dictionary notifications', function() eq(1, eval('g:called')) end) + it('does not crash when using dictwatcherdel in callback', function() + source([[ + let g:d = {} + + function! W1(...) + " Delete current and following watcher. + call dictwatcherdel(g:d, '*', function('W1')) + call dictwatcherdel(g:d, '*', function('W2')) + try + call dictwatcherdel({}, 'meh', function('tr')) + catch + let g:exc = v:exception + endtry + endfunction + call dictwatcheradd(g:d, '*', function('W1')) + + function! W2(...) + endfunction + call dictwatcheradd(g:d, '*', function('W2')) + + let g:d.foo = 23 + ]]) + eq(23, eval('g:d.foo')) + eq("Vim(call):Couldn't find a watcher matching key and callback", eval('g:exc')) + end) + + it('does not call watcher added in callback', function() + source([[ + let g:d = {} + let g:calls = [] + + function! W1(...) abort + call add(g:calls, 'W1') + call dictwatcheradd(g:d, '*', function('W2')) + endfunction + + function! W2(...) abort + call add(g:calls, 'W2') + endfunction + + call dictwatcheradd(g:d, '*', function('W1')) + let g:d.foo = 23 + ]]) + eq(23, eval('g:d.foo')) + eq({"W1"}, eval('g:calls')) + end) + + it('calls watcher deleted in callback', function() + source([[ + let g:d = {} + let g:calls = [] + + function! W1(...) abort + call add(g:calls, "W1") + call dictwatcherdel(g:d, '*', function('W2')) + endfunction + + function! W2(...) abort + call add(g:calls, "W2") + endfunction + + call dictwatcheradd(g:d, '*', function('W1')) + call dictwatcheradd(g:d, '*', function('W2')) + let g:d.foo = 123 + + unlet g:d + let g:d = {} + call dictwatcheradd(g:d, '*', function('W2')) + call dictwatcheradd(g:d, '*', function('W1')) + let g:d.foo = 123 + ]]) + eq(123, eval('g:d.foo')) + eq({"W1", "W2", "W2", "W1"}, eval('g:calls')) + end) + end) diff --git a/test/functional/ex_cmds/excmd_spec.lua b/test/functional/ex_cmds/excmd_spec.lua index aac2a9f469..33794eb50d 100644 --- a/test/functional/ex_cmds/excmd_spec.lua +++ b/test/functional/ex_cmds/excmd_spec.lua @@ -24,8 +24,6 @@ describe('Ex cmds', function() pcall_err(command, ':menu 9999999999999999999999999999999999999999')) eq('Vim(bdelete):E939: Positive count required', pcall_err(command, ':bdelete 9999999999999999999999999999999999999999')) - eq('Vim(retab):E487: Argument must be positive', - pcall_err(command, ':retab 9999999999999999999999999999999999999999')) assert_alive() end) end) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua new file mode 100644 index 0000000000..16d0dfb6a1 --- /dev/null +++ b/test/functional/ex_cmds/source_spec.lua @@ -0,0 +1,47 @@ +local helpers = require('test.functional.helpers')(after_each) +local command = helpers.command +local insert = helpers.insert +local eq = helpers.eq +local clear = helpers.clear +local meths = helpers.meths +local feed = helpers.feed +local feed_command = helpers.feed_command + +describe(':source', function() + before_each(function() + clear() + end) + + it('current buffer', function() + insert('let a = 2') + command('source') + eq('2', meths.exec('echo a', true)) + end) + + it('selection in current buffer', function() + insert( + 'let a = 2\n'.. + 'let a = 3\n'.. + 'let a = 4\n') + + -- Source the 2nd line only + feed('ggjV') + feed_command(':source') + eq('3', meths.exec('echo a', true)) + + -- Source from 2nd line to end of file + feed('ggjVG') + feed_command(':source') + eq('4', meths.exec('echo a', true)) + end) + + it('multiline heredoc command', function() + insert( + 'lua << EOF\n'.. + 'y = 4\n'.. + 'EOF\n') + + command('source') + eq('4', meths.exec('echo luaeval("y")', true)) + end) +end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 252db88b6b..bcd5e22492 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -70,11 +70,11 @@ local function expect_notification(method, params, ...) ..., "expect_notification", "message") end -local function expect_request(method, callback, ...) +local function expect_request(method, handler, ...) local req = read_message() assert_eq(method, req.method, ..., "expect_request", "method") - local err, result = callback(req.params) + local err, result = handler(req.params) respond(req.id, err, result) end @@ -154,6 +154,7 @@ function tests.capabilities_for_client_supports_method() hoverProvider = true; definitionProvider = false; referencesProvider = false; + codeLensProvider = { resolveProvider = true; }; } } end; @@ -402,11 +403,11 @@ function tests.basic_check_buffer_open_and_change_incremental() contentChanges = { { range = { - start = { line = 1; character = 0; }; - ["end"] = { line = 2; character = 0; }; + start = { line = 1; character = 3; }; + ["end"] = { line = 1; character = 3; }; }; - rangeLength = 4; - text = "boop\n"; + rangeLength = 0; + text = "boop"; }; } }) diff --git a/test/functional/fixtures/smile2.cat b/test/functional/fixtures/smile2.cat new file mode 100644 index 0000000000..0feb32f293 --- /dev/null +++ b/test/functional/fixtures/smile2.cat @@ -0,0 +1,32 @@ +31,79 +[?25l[H[2J:smile
+ [103moooo$$$$$$$$$$$$oooo(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$o(B[m
+ [103moo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103mo$(B[m [103m$$(B[m [103mo$(B[m
+ [103mo(B[m [103m$(B[m [103moo(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o(B[m [103m$$(B[m [103m$$(B[m [103m$$o$(B[m
+ [103moo(B[m [103m$(B[m [103m$(B[m "[103m$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$o(B[m [103m$$$o$$o$(B[m
+ "[103m$$$$$$o$(B[m [103mo$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$o(B[m [103m$$$$$$$$(B[m
+ [103m$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$(B[m [103m$$$$$$$$$$$$$$(B[m """[103m$$$(B[m
+ "[103m$$$(B[m""""[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$(B[m
+ [103m$$$(B[m [103mo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m "[103m$$$o(B[m
+ [103mo$$(B[m" [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$o(B[m
+ [103m$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" "[103m$$$$$$ooooo$$$$o(B[m
+ [103mo$$$oooo$$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103mo$$$$$$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$(B[m"[103m$$$$(B[m [103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m [103m$$$$(B[m""""""""
+ """" [103m$$$$(B[m "[103m$$$$$$$$$$$$$$$$$$$$$$$$$$$$(B[m" [103mo$$$(B[m
+ "[103m$$$o(B[m """[103m$$$$$$$$$$$$$$$$$$(B[m"[103m$$(B[m" [103m$$$(B[m
+ [103m$$$o(B[m "[103m$$(B[m""[103m$$$$$$(B[m"""" [103mo$$$(B[m
+ [103m$$$$o(B[m [103mo$$$(B[m"
+ "[103m$$$$o(B[m [103mo$$$$$$o(B[m"[103m$$$$o(B[m [103mo$$$$(B[m
+ "[103m$$$$$oo(B[m ""[103m$$$$o$$$$$o(B[m [103mo$$$$(B[m""
+ ""[103m$$$$$oooo(B[m "[103m$$$o$$$$$$$$$(B[m"""
+ ""[103m$$$$$$$oo(B[m [103m$$$$$$$$$$(B[m
+ """"[103m$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$$$(B[m
+ [103m$$$$$$$$$$(B[m"
+ "[103m$$$(B[m""""
+
+[32mPress ENTER or type command to continue(B[m
+(B[0;1;7mterm://~/config2/docs/pres//32693:vim --clean +smile 29,39 All
+(B[m:call nvim__screenshot("smile2.cat") [?25h
\ No newline at end of file diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index 5f7bbd887f..97ac96804e 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -157,8 +157,8 @@ describe('memory usage', function() -- The usage may be a bit less than the last value, use 80%. -- Allow for 20% tolerance at the upper limit. That's very permissive, but -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to - -- be even more permissive. - local upper_multiplier = uname() == 'freebsd' and 15 or 12 + -- be even much more permissive. + local upper_multiplier = uname() == 'freebsd' and 19 or 12 local lower = before.last * 8 / 10 local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10) check_result({before=before, after=after, last=last}, diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index 1db7afc7a7..023cdd4ae1 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -1,6 +1,10 @@ +-- See also: src/nvim/testdir/test_options.vim local helpers = require('test.functional.helpers')(after_each) local command, clear = helpers.command, helpers.clear local source, expect = helpers.source, helpers.expect +local exc_exec = helpers.exc_exec; +local matches = helpers.matches; +local Screen = require('test.functional.ui.screen') describe('options', function() setup(clear) @@ -11,7 +15,7 @@ describe('options', function() end) describe('set', function() - setup(clear) + before_each(clear) it("should keep two comma when 'path' is changed", function() source([[ @@ -24,4 +28,59 @@ describe('set', function() foo,,bar]]) end) + + it('winminheight works', function() + local screen = Screen.new(20, 11) + screen:attach() + source([[ + set wmh=0 stal=2 + below sp | wincmd _ + below sp | wincmd _ + below sp | wincmd _ + below sp + ]]) + matches('E36: Not enough room', exc_exec('set wmh=1')) + end) + + it('winminheight works with tabline', function() + local screen = Screen.new(20, 11) + screen:attach() + source([[ + set wmh=0 stal=2 + split + split + split + split + tabnew + ]]) + matches('E36: Not enough room', exc_exec('set wmh=1')) + end) + + it('scroll works', function() + local screen = Screen.new(42, 16) + screen:attach() + source([[ + set scroll=2 + set laststatus=2 + ]]) + command('verbose set scroll?') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + scroll=7 | + Last set from changed window size | + Press ENTER or type command to continue^ | + ]]) + end) end) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 67dc5f5a16..5da8452a51 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -1,5 +1,6 @@ -- Test suite for testing interactions with API bindings local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') local command = helpers.command local meths = helpers.meths @@ -9,8 +10,9 @@ local eq = helpers.eq local fail = helpers.fail local exec_lua = helpers.exec_lua local feed = helpers.feed -local deepcopy = helpers.deepcopy local expect_events = helpers.expect_events +local write_file = helpers.write_file +local dedent = helpers.dedent local origlines = {"original line 1", "original line 2", @@ -20,19 +22,20 @@ 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 return true end end - local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes, preview=preview} + local opts = {[evname]=callback, on_detach=callback, on_reload=callback, utf_sizes=utf_sizes, preview=preview} if changedtick then opts.on_changedtick = callback end @@ -44,41 +47,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 +84,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 +126,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 +174,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([[ @@ -225,6 +244,14 @@ describe('lua buffer event callbacks: on_lines', function() eq(1, meths.get_var('listener_cursor_line')) end) + it('has valid cursor position while deleting lines', function() + meths.buf_set_lines(0, 0, -1, true, { "line_1", "line_2", "line_3", "line_4"}) + meths.win_set_cursor(0, {2, 0}) + eq(2, meths.win_get_cursor(0)[1]) + meths.buf_set_lines(0, 0, -1, true, { "line_1", "line_2", "line_3"}) + eq(2, meths.win_get_cursor(0)[1]) + end) + it('does not SEGFAULT when calling win_findbuf in on_detach', function() exec_lua[[ @@ -272,26 +299,24 @@ 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 -- test both ways. local function setup_eventcheck(verify, start_txt) - meths.buf_set_lines(0, 0, -1, true, start_txt) - local shadow = deepcopy(start_txt) - local shadowbytes = table.concat(shadow, '\n') .. '\n' + if start_txt then + meths.buf_set_lines(0, 0, -1, true, start_txt) + else + start_txt = meths.buf_get_lines(0, 0, -1, true) + end + local shadowbytes = table.concat(start_txt, '\n') .. '\n' -- TODO: while we are brewing the real strong coffe, -- verify should check buf_get_offset after every check_events if verify then 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" @@ -318,6 +343,8 @@ describe('lua: nvim_buf_attach on_bytes', function() local unknown = string.rep('\255', new_byte) local after = string.sub(shadowbytes, start_byte + old_byte + 1) shadowbytes = before .. unknown .. after + elseif event[1] == verify_name and event[2] == "reload" then + shadowbytes = table.concat(meths.buf_get_lines(0, 0, -1, true), '\n') .. '\n' end end @@ -434,6 +461,36 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("deleting lines", function() + local check_events = setup_eventcheck(verify, origlines) + + feed("dd") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 16, 0, 0, 0 }; + } + + feed("d2j") + + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 3, 0, 48, 0, 0, 0 }; + } + + feed("ld<c-v>2j") + + check_events { + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 1, 1, 16, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 2, 1, 31, 0, 1, 1, 0, 0, 0 }; + } + + feed("vjwd") + + check_events { + { "test1", "bytes", 1, 10, 0, 1, 1, 1, 9, 23, 0, 0, 0 }; + } + end) + it("changing lines", function() local check_events = setup_eventcheck(verify, origlines) @@ -495,20 +552,79 @@ describe('lua: nvim_buf_attach on_bytes', function() end) - it('inccomand=nosplit and substitute', function() - if verify then pending("Verification can't be done when previewing") end + it("linewise paste", function() + local check_events = setup_eventcheck(verify, origlines) + + feed'yyp' + check_events { + { "test1", "bytes", 1, 3, 1, 0, 16, 0, 0, 0, 1, 0, 16 }; + } + + feed'Gyyp' + check_events { + { "test1", "bytes", 1, 4, 8, 0, 130, 0, 0, 0, 1, 0, 18 }; + } + end) - local check_events = setup_eventcheck(verify, {"abcde"}) + it('inccomand=nosplit and substitute', function() + local check_events = setup_eventcheck(verify, + {"abcde", "12345"}) meths.set_option('inccommand', 'nosplit') - feed ':%s/bcd/' + -- linewise substitute + 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' + 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 }; + } + + feed("<esc>") + + -- splitting lines + feed([[:%s/abc/\r]]) + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 1, 0, 1 }; + { "test1", "bytes", 1, 6, 0, 0, 0, 1, 0, 1, 0, 3, 3 }; + } + + feed("<esc>") + -- multi-line regex + feed([[:%s/de\n123/a]]) + + check_events { + { "test1", "bytes", 1, 3, 0, 3, 3, 1, 3, 6, 0, 1, 1 }; + { "test1", "bytes", 1, 6, 0, 3, 3, 0, 1, 1, 1, 3, 6 }; + } + + feed("<esc>") + -- replacing with unicode + feed(":%s/b/→") + + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 3, 3 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 3, 3, 0, 1, 1 }; + } + + feed("<esc>") + -- replacing with escaped characters + feed([[:%s/b/\\]]) + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + } + + feed("<esc>") + -- replacing with expression register + feed([[:%s/b/\=5+5]]) + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }; } end) @@ -575,6 +691,256 @@ describe('lua: nvim_buf_attach on_bytes', function() "original line 5", "original line 6" }, meths.buf_get_lines(0, 0, -1, true)) end) + + it('checktime autoread', function() + write_file("Xtest-reload", dedent [[ + old line 1 + old line 2]]) + lfs.touch("Xtest-reload", os.time() - 10) + command "e Xtest-reload" + command "set autoread" + + local check_events = setup_eventcheck(verify, nil) + + write_file("Xtest-reload", dedent [[ + new line 1 + new line 2 + new line 3]]) + + command "checktime" + check_events { + { "test1", "reload", 1 }; + } + + feed 'ggJ' + check_events { + { "test1", "bytes", 1, 5, 0, 10, 10, 1, 0, 1, 0, 1, 1 }; + } + + eq({'new line 1 new line 2', 'new line 3'}, meths.buf_get_lines(0, 0, -1, true)) + + -- check we can undo and redo a reload event. + feed 'u' + check_events { + { "test1", "bytes", 1, 8, 0, 10, 10, 0, 1, 1, 1, 0, 1 }; + } + + feed 'u' + check_events { + { "test1", "reload", 1 }; + } + + feed '<c-r>' + check_events { + { "test1", "reload", 1 }; + } + + feed '<c-r>' + check_events { + { "test1", "bytes", 1, 14, 0, 10, 10, 1, 0, 1, 0, 1, 1 }; + } + end) + + it("tab with noexpandtab and softtabstop", function() + command("set noet") + command("set ts=4") + command("set sw=2") + command("set sts=4") + + local check_events = setup_eventcheck(verify, {'asdfasdf'}) + + feed("gg0i<tab>") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 4, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, + } + feed("<tab>") + + -- when spaces are merged into a tabstop + check_events { + { "test1", "bytes", 1, 5, 0, 2, 2, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 6, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 7, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, + } + + + feed("<esc>u") + check_events { + { "test1", "bytes", 1, 8, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 8, 0, 0, 0, 0, 4, 4, 0, 0, 0 } + } + + -- in REPLACE mode + feed("R<tab><tab>") + check_events { + { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 10, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 11, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 12, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 13, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, + } + feed("<esc>u") + check_events { + { "test1", "bytes", 1, 14, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 14, 0, 2, 2, 0, 2, 2, 0, 1, 1 }, + { "test1", "bytes", 1, 14, 0, 0, 0, 0, 2, 2, 0, 1, 1 } + } + + -- in VISUALREPLACE mode + feed("gR<tab><tab>") + check_events { + { "test1", "bytes", 1, 15, 0, 0, 0, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 16, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 17, 0, 2, 2, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 18, 0, 3, 3, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 19, 0, 3, 3, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 20, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 22, 0, 2, 2, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 23, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 25, 0, 1, 1, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 26, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 28, 0, 0, 0, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 29, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 31, 0, 0, 0, 0, 4, 4, 0, 1, 1 }; + } + + -- inserting tab after other tabs + command("set sw=4") + feed("<esc>0a<tab>") + check_events { + { "test1", "bytes", 1, 32, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 33, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 34, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 35, 0, 4, 4, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 36, 0, 1, 1, 0, 4, 4, 0, 1, 1 }; + } + end) + + it("retab", function() + command("set noet") + command("set ts=4") + + local check_events = setup_eventcheck(verify, {" asdf"}) + command("retab 8") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 7, 7, 0, 9, 9 }; + } + end) + + it("sends events when undoing with undofile", function() + write_file("Xtest-undofile", dedent([[ + 12345 + hello world + ]])) + + command("e! Xtest-undofile") + command("set undodir=. | set undofile") + + local ns = helpers.request('nvim_create_namespace', "ns1") + meths.buf_set_extmark(0, ns, 0, 0, {}) + + eq({"12345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + -- splice + feed("gg0d2l") + + eq({"345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + -- move + command(".m+1") + + eq({"hello world", "345"}, meths.buf_get_lines(0, 0, -1, true)) + + -- reload undofile and undo changes + command("w") + command("set noundofile") + command("bw!") + command("e! Xtest-undofile") + + command("set undofile") + + local check_events = setup_eventcheck(verify, nil) + + feed("u") + eq({"345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + check_events { + { "test1", "bytes", 2, 6, 1, 0, 12, 1, 0, 4, 0, 0, 0 }, + { "test1", "bytes", 2, 6, 0, 0, 0, 0, 0, 0, 1, 0, 4 } + } + + feed("u") + eq({"12345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + check_events { + { "test1", "bytes", 2, 8, 0, 0, 0, 0, 0, 0, 0, 2, 2 } + } + command("bw!") + end) + + it("blockwise paste with uneven line lengths", function() + local check_events = setup_eventcheck(verify, {'aaaa', 'aaa', 'aaa'}) + + -- eq({}, meths.buf_get_lines(0, 0, -1, true)) + feed("gg0<c-v>jj$d") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 4, 4, 0, 0, 0 }, + { "test1", "bytes", 1, 3, 1, 0, 1, 0, 3, 3, 0, 0, 0 }, + { "test1", "bytes", 1, 3, 2, 0, 2, 0, 3, 3, 0, 0, 0 }, + } + + feed("p") + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 0, 0, 0, 4, 4 }, + { "test1", "bytes", 1, 4, 1, 0, 5, 0, 0, 0, 0, 3, 3 }, + { "test1", "bytes", 1, 4, 2, 0, 9, 0, 0, 0, 0, 3, 3 }, + } + + end) + + it(":luado", function() + local check_events = setup_eventcheck(verify, {"abc", "12345"}) + + command(".luado return 'a'") + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 0, 1, 1 }; + } + + command("luado return 10") + + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 1, 1, 0, 2, 2 }; + { "test1", "bytes", 1, 5, 1, 0, 3, 0, 5, 5, 0, 2, 2 }; + } + + end) + + it("flushes deleted bytes on move", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB", "CCC", "DDD"}) + + feed(":.move+1<cr>") + + check_events { + { "test1", "bytes", 1, 5, 0, 0, 0, 1, 0, 4, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 1, 0, 4, 0, 0, 0, 1, 0, 4 }; + } + + feed("jd2j") + + check_events { + { "test1", "bytes", 1, 6, 2, 0, 8, 2, 0, 8, 0, 0, 0 }; + } + end) + + teardown(function() + os.remove "Xtest-reload" + os.remove "Xtest-undofile" + os.remove ".Xtest-undofile.un~" + end) end describe('(with verify) handles', function() 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/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index e253db5297..9bf00b594b 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -715,6 +715,11 @@ describe('lua stdlib', function() eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) end) + it('vim.fn should error when calling API function', function() + eq('Error executing lua: vim.lua:0: Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', + pcall_err(exec_lua, "vim.fn.nvim_get_current_line()")) + end) + it('vim.rpcrequest and vim.rpcnotify', function() exec_lua([[ chan = vim.fn.jobstart({'cat'}, {rpc=true}) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 4705a76465..8c91c4ab2c 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -208,6 +208,69 @@ describe('vim.lsp.diagnostic', function() ]])) end) + describe('reset', function() + it('diagnostic count is 0 and displayed diagnostics are 0 after call', function() + -- 1 Error (1) + -- 1 Warning (2) + -- 1 Warning (2) + 1 Warning (1) + -- 2 highlights and 2 underlines (since error) + -- 1 highlight + 1 underline + local all_highlights = {1, 1, 2, 4, 2} + eq(all_highlights, exec_lua [[ + local server_1_diags = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 2, 1, 2, 5), + } + local server_2_diags = { + make_warning("Warning 1", 2, 1, 2, 5), + } + + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_1_diags }, 1) + vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_2_diags }, 2) + return { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } + ]]) + + -- Reset diagnostics from server 1 + exec_lua([[ vim.lsp.diagnostic.reset(1, { [ diagnostic_bufnr ] = { [ 1 ] = true ; [ 2 ] = true } } )]]) + + -- Make sure we have the right diagnostic count + eq({0, 1, 1, 0, 2} , exec_lua [[ + local diagnostic_count = {} + vim.wait(100, function () diagnostic_count = { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } end ) + return diagnostic_count + ]]) + + -- Reset diagnostics from server 2 + exec_lua([[ vim.lsp.diagnostic.reset(2, { [ diagnostic_bufnr ] = { [ 1 ] = true ; [ 2 ] = true } } )]]) + + -- Make sure we have the right diagnostic count + eq({0, 0, 0, 0, 0}, exec_lua [[ + local diagnostic_count = {} + vim.wait(100, function () diagnostic_count = { + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2), + vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil), + count_of_extmarks_for_client(diagnostic_bufnr, 1), + count_of_extmarks_for_client(diagnostic_bufnr, 2), + } end ) + return diagnostic_count + ]]) + + end) + end) + describe('get_next_diagnostic_pos', function() it('can find the next pos with only one client', function() eq({1, 1}, exec_lua [[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 41fdf845df..557f8a206f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -11,6 +11,8 @@ local pesc = helpers.pesc local insert = helpers.insert local retry = helpers.retry local NIL = helpers.NIL +local read_file = require('test.helpers').read_file +local write_file = require('test.helpers').write_file -- Use these to get access to a coroutine so that I can run async tests and use -- yield. @@ -27,13 +29,24 @@ teardown(function() os.remove(fake_lsp_logfile) end) -local function fake_lsp_server_setup(test_name, timeout_ms) +local function clear_notrace() + -- problem: here be dragons + -- solution: don't look for dragons to closely + clear {env={ + NVIM_LUA_NOTRACK="1"; + VIMRUNTIME=os.getenv"VIMRUNTIME"; + }} +end + + +local function fake_lsp_server_setup(test_name, timeout_ms, options) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout = ... + local test_name, fixture_filename, logfile, timeout, options = ... TEST_RPC_CLIENT_ID = lsp.start_client { cmd_env = { NVIM_LOG_FILE = logfile; + NVIM_LUA_NOTRACK = "1"; }; cmd = { vim.v.progpath, '-Es', '-u', 'NONE', '--headless', @@ -41,10 +54,10 @@ local function fake_lsp_server_setup(test_name, timeout_ms) "-c", string.format("lua TIMEOUT = %d", timeout), "-c", "luafile "..fixture_filename, }; - callbacks = setmetatable({}, { + handlers = setmetatable({}, { __index = function(t, method) return function(...) - return vim.rpcrequest(1, 'callback', ...) + return vim.rpcrequest(1, 'handler', ...) end end; }); @@ -52,18 +65,19 @@ local function fake_lsp_server_setup(test_name, timeout_ms) on_init = function(client, result) TEST_RPC_CLIENT = client vim.rpcrequest(1, "init", result) + client.config.flags.allow_incremental_sync = options.allow_incremental_sync or false end; on_exit = function(...) vim.rpcnotify(1, "exit", ...) end; } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3) + ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}) end local function test_rpc_server(config) if config.test_name then - clear() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3) + clear_notrace() + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options) end local client = setmetatable({}, { __index = function(_, name) @@ -89,7 +103,7 @@ local function test_rpc_server(config) end return NIL end - if method == 'callback' then + if method == 'handler' then if config.on_callback then config.on_callback(unpack(args)) end @@ -117,7 +131,7 @@ end describe('LSP', function() describe('server_name specified', function() before_each(function() - clear() + clear_notrace() -- Run an instance of nvim on the file which contains our "scripts". -- Pass TEST_NAME to pick the script. local test_name = "basic_init" @@ -193,13 +207,19 @@ 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}; } test_rpc_server { test_name = "basic_init"; - on_init = function(client, _init_result) + on_init = function(client, _) -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. @@ -241,6 +261,10 @@ describe('LSP', function() end) it('should succeed with manual shutdown', function() + if 'openbsd' == helpers.uname() then + pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204') + return + end local expected_callbacks = { {NIL, "shutdown", {}, 1, NIL}; {NIL, "test", {}, 1}; @@ -251,6 +275,7 @@ describe('LSP', function() eq(0, client.resolved_capabilities().text_document_did_change) client.request('shutdown') client.notify('exit') + client.stop() end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -304,11 +329,9 @@ describe('LSP', function() } end) it('workspace/configuration returns NIL per section if client was started without config.settings', function() + clear_notrace() fake_lsp_server_setup('workspace/configuration no settings') - eq({ - NIL, - NIL, - }, exec_lua [[ + eq({ NIL, NIL, }, exec_lua [[ local params = { items = { {section = 'foo'}, @@ -329,6 +352,9 @@ describe('LSP', function() client.stop() local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_save) + eq(false, client.resolved_capabilities().code_lens) + eq(false, client.resolved_capabilities().code_lens_resolve) end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -354,6 +380,8 @@ describe('LSP', function() eq(true, client.resolved_capabilities().hover) eq(false, client.resolved_capabilities().goto_definition) eq(false, client.resolved_capabilities().rename) + eq(true, client.resolved_capabilities().code_lens) + eq(true, client.resolved_capabilities().code_lens_resolve) -- known methods for resolved capabilities eq(true, client.supports_method("textDocument/hover")) @@ -382,7 +410,7 @@ describe('LSP', function() exec_lua([=[ BUFFER = vim.api.nvim_get_current_buf() lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.lsp.callbacks['textDocument/typeDefinition'] = function(err, method) + vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method) vim.lsp._last_lsp_callback = { err = err; method = method } end vim.lsp._unsupported_method = function(method) @@ -421,7 +449,7 @@ describe('LSP', function() test_name = "capabilities_for_client_supports_method"; on_setup = function() exec_lua([=[ - vim.lsp.callbacks['textDocument/typeDefinition'] = function(err, method) + vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method) vim.lsp._last_lsp_callback = { err = err; method = method } end vim.lsp._unsupported_method = function(method) @@ -675,8 +703,7 @@ describe('LSP', function() } end) - -- TODO(askhan) we don't support full for now, so we can disable these tests. - pending('should check the body and didChange incremental', function() + it('should check the body and didChange incremental', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; {NIL, "finish", {}, 1}; @@ -685,6 +712,7 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; + options = { allow_incremental_sync = true }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -711,7 +739,7 @@ describe('LSP', function() if method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + "123boop"; }) ]] client.notify('finish') @@ -893,8 +921,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_callback = function(err, method, params, client_id) - eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + on_handler = function(err, method, params, client_id) + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected handler") end; } end) @@ -928,7 +956,7 @@ end) describe('LSP', function() before_each(function() - clear() + clear_notrace() end) local function make_edit(y_0, x_0, y_1, x_1, text) @@ -1028,6 +1056,20 @@ describe('LSP', function() 'å ä ɧ 汉语 ↥ 🤦 🦄'; }, buf_lines(1)) end) + it('applies text edits at the end of the document', function() + local edits = { + make_edit(5, 0, 5, 0, "foobar"); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + 'foobar'; + }, buf_lines(1)) + end) describe('with LSP end line after what Vim considers to be the end line', function() it('applies edits when the last linebreak is considered a new line', function() @@ -1082,14 +1124,14 @@ describe('LSP', function() '2nd line of 语text'; }, buf_lines(target_bufnr)) end) - it('correctly goes ahead with the edit if the version is vim.NIL', function() - -- we get vim.NIL when we decode json null value. - local json = exec_lua[[ - return vim.fn.json_decode("{ \"a\": 1, \"b\": null }") - ]] - eq(json.b, exec_lua("return vim.NIL")) - - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL"))) + it('always accepts edit with version = 0', function() + exec_lua([[ + local args = {...} + local bufnr = select(1, ...) + local text_edit = select(2, ...) + vim.lsp.util.buf_versions[bufnr] = 10 + vim.lsp.util.apply_text_document_edit(text_edit) + ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of 语text'; @@ -1238,6 +1280,99 @@ describe('LSP', function() return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) end) + it('Supports file creation with CreateFile payload', function() + local tmpfile = helpers.tmpname() + os.remove(tmpfile) -- Should not exist, only interested in a tmpname + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + end) + it('createFile does not touch file if it exists and ignoreIfExists is set', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Dummy content') + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + options = { + ignoreIfExists = true, + }, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq('Dummy content', read_file(tmpfile)) + end) + it('createFile overrides file if overwrite is set', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Dummy content') + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'create', + uri = uri, + options = { + overwrite = true, + ignoreIfExists = true, -- overwrite must win over ignoreIfExists + }, + }, + } + } + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq('', read_file(tmpfile)) + end) + it('DeleteFile delete file and buffer', function() + local tmpfile = helpers.tmpname() + write_file(tmpfile, 'Be gone') + local uri = exec_lua([[ + local fname = select(1, ...) + local bufnr = vim.fn.bufadd(fname) + vim.fn.bufload(bufnr) + return vim.uri_from_fname(fname) + ]], tmpfile) + local edit = { + documentChanges = { + { + kind = 'delete', + uri = uri, + } + } + } + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) + end) + it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() + local tmpfile = helpers.tmpname() + os.remove(tmpfile) + local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) + local edit = { + documentChanges = { + { + kind = 'delete', + uri = uri, + options = { + ignoreIfNotExists = false, + } + } + } + } + eq(false, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + end) end) describe('completion_list_to_complete_items', function() @@ -1284,6 +1419,72 @@ describe('LSP', function() end) end) + describe('lsp.util.rename', function() + it('Can rename an existing file', function() + local old = helpers.tmpname() + write_file(old, 'Test content') + local new = helpers.tmpname() + os.remove(new) -- only reserve the name, file must not exist for the test scenario + local lines = exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + vim.lsp.util.rename(old, new) + + -- after rename the target file must have the contents of the source file + local bufnr = vim.fn.bufadd(new) + return vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) + ]], old, new) + eq({'Test content'}, lines) + local exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', old) + eq(false, exists) + exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', new) + eq(true, exists) + os.remove(new) + end) + it('Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() + local old = helpers.tmpname() + write_file(old, 'Old File') + local new = helpers.tmpname() + write_file(new, 'New file') + + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { ignoreIfExists = true }) + ]], old, new) + + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq('New file', read_file(new)) + + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { overwrite = false }) + ]], old, new) + + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq('New file', read_file(new)) + end) + it('Does override target if overwrite is true', function() + local old = helpers.tmpname() + write_file(old, 'Old file') + local new = helpers.tmpname() + write_file(new, 'New file') + exec_lua([[ + local old = select(1, ...) + local new = select(2, ...) + + vim.lsp.util.rename(old, new, { overwrite = true }) + ]], old, new) + + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new)) + eq('Old file\n', read_file(new)) + end) + end) + describe('lsp.util.locations_to_items', function() it('Convert Location[] to items', function() local expected = { @@ -1761,8 +1962,8 @@ describe('LSP', function() uri = "file:///src/main.rs" } } } - local callback = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] - callback(nil, nil, rust_analyzer_response) + local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] + handler(nil, nil, rust_analyzer_response) return vim.fn.getqflist() ]=]) @@ -1833,8 +2034,8 @@ describe('LSP', function() } } } } - local callback = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] - callback(nil, nil, rust_analyzer_response) + local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] + handler(nil, nil, rust_analyzer_response) return vim.fn.getqflist() ]=]) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 209537831f..c61bf108cb 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -258,6 +258,13 @@ describe(':terminal buffer', function() it('handles wqall', function() eq('Vim(wqall):E948: Job still running', exc_exec('wqall')) end) + + it('does not segfault when pasting empty buffer #13955', function() + feed_command('terminal') + feed('<c-\\><c-n>') + feed_command('put a') -- buffer a is empty + helpers.assert_alive() + end) end) describe('No heap-buffer-overflow when using', function() diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index cb73bfbbe1..05e0c5fe2c 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -445,7 +445,7 @@ describe('treesitter highlighting', function() exec_lua [[ local parser = vim.treesitter.get_parser(0, "c", { - queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"} + injections = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"} }) local highlighter = vim.treesitter.highlighter test_hl = highlighter.new(parser, {queries = {c = hl_query}}) @@ -472,4 +472,102 @@ describe('treesitter highlighting', function() | ]]} end) + + it("supports overriding queries, like ", function() + if pending_c_parser(pending) then return end + + insert([[ + int x = INT_MAX; + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define foo void main() { \ + return 42; \ + } + ]]) + + exec_lua [[ + local injection_query = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)" + require('vim.treesitter.query').set_query("c", "highlights", hl_query) + require('vim.treesitter.query').set_query("c", "injections", injection_query) + + vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, "c")) + ]] + + screen:expect{grid=[[ + {3:int} x = {5:INT_MAX}; | + #define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))| + #define foo {3:void} main() { \ | + {4:return} {5:42}; \ | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) + + it("supports highlighting with custom highlight groups", function() + if pending_c_parser(pending) then return end + + insert(hl_text) + + exec_lua [[ + local parser = vim.treesitter.get_parser(0, "c") + test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}}) + ]] + + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + + -- This will change ONLY the literal strings to look like comments + -- The only literal string is the "vim.schedule: expected function" in this test. + exec_lua [[vim.cmd("highlight link cString comment")]] + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:lua_pushliteral}(lstate, {2:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + screen:expect{ unchanged=true } + end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index a5801271cb..afb17dd2cf 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -45,7 +45,7 @@ describe('treesitter API', function() return {keys, lang.fields, symbols} ]])) - eq({fields=true, symbols=true}, keys) + eq({fields=true, symbols=true, _abi_version=true}, keys) local fset = {} for _,f in pairs(fields) do diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index f99362fbdf..72ff6f2fb6 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -468,7 +468,7 @@ int x = INT_MAX; it("should inject a language", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { - queries = { + injections = { c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) ]]) @@ -489,7 +489,7 @@ int x = INT_MAX; it("should inject a language", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { - queries = { + injections = { c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) ]]) @@ -506,11 +506,39 @@ int x = INT_MAX; end) end) + describe("when providing parsing information through a directive", function() + it("should inject a language", function() + exec_lua([=[ + vim.treesitter.add_directive("inject-clang!", function(match, _, _, pred, metadata) + metadata.language = "c" + metadata.combined = true + metadata.content = pred[2] + end) + + parser = vim.treesitter.get_parser(0, "c", { + injections = { + c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" .. + "(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}}) + ]=]) + + eq("table", exec_lua("return type(parser:children().c)")) + eq(2, exec_lua("return #parser:children().c:trees()")) + eq({ + {0, 0, 7, 0}, -- root tree + {3, 14, 5, 18}, -- VALUE 123 + -- VALUE1 123 + -- VALUE2 123 + {1, 26, 2, 68} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + }, get_ranges()) + end) + end) + describe("when using the offset directive", function() it("should shift the range by the directive amount", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { - queries = { + injections = { c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}}) ]]) @@ -538,7 +566,7 @@ int x = INT_MAX; it("should return the correct language tree", function() local result = exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { - queries = { c = "(preproc_def (preproc_arg) @c)"}}) + injections = { c = "(preproc_def (preproc_arg) @c)"}}) local sub_tree = parser:language_for_range({1, 18, 1, 19}) @@ -572,28 +600,5 @@ int x = INT_MAX; eq(result, "value") end) end) - - describe("when setting for a capture match", function() - it("should set/get the data correctly", function() - insert([[ - int x = 3; - ]]) - - local result = exec_lua([[ - local result - - 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) do - result = metadata[pattern].key - end - - return result - ]]) - - eq(result, "value") - end) - end) end) end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 21c01b3458..0ea8bab957 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed = helpers.clear, helpers.feed local source = helpers.source local command = helpers.command +local assert_alive = helpers.assert_alive local function new_screen(opt) local screen = Screen.new(25, 5) @@ -758,6 +759,7 @@ local function test_cmdline(linegrid) end) it("doesn't send invalid events when aborting mapping #10000", function() + command('set notimeout') command('cnoremap ab c') feed(':xa') @@ -842,3 +844,14 @@ describe('cmdline redraw', function() ]], unchanged=true} end) end) + +describe("cmdline height", function() + it("does not crash resized screen #14263", function() + clear() + local screen = Screen.new(25, 10) + screen:attach() + command('set cmdheight=9999') + screen:try_resize(25, 5) + assert_alive() + end) +end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index e1a72ced05..f75f700fb5 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -212,10 +212,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 55 + m.hl_id = 56 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 56 end + if m.id_lm then m.id_lm = 57 end end -- Assert the new expectation. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 781fdf7203..82d3075be2 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -8,6 +8,7 @@ local exec_lua = helpers.exec_lua local exec = helpers.exec local expect_events = helpers.expect_events local meths = helpers.meths +local command = helpers.command describe('decorations providers', function() local screen @@ -28,6 +29,7 @@ describe('decorations providers', function() [10] = {italic = true, background = Screen.colors.Magenta}; [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; [12] = {foreground = tonumber('0x990000')}; + [13] = {background = Screen.colors.LightBlue}; } end) @@ -178,7 +180,7 @@ describe('decorations providers', function() | ]]} - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) screen:expect{grid=[[ {10: 1 }{11:// just to see if there was an accid}| {10: }{11:ent} | @@ -204,7 +206,7 @@ describe('decorations providers', function() local ns2 = a.nvim_create_namespace 'ns2' a.nvim_set_decoration_provider (ns2, { on_win = function (_, win, buf) - a.nvim_set_hl_ns(win == thewin and _G.ns1 or ns2) + a.nvim__set_hl_ns(win == thewin and _G.ns1 or ns2) end; }) ]] @@ -251,7 +253,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue'}) - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) screen:expect{grid=[[ // just to see if there was an accident | @@ -287,7 +289,7 @@ describe('decorations providers', function() ]]} meths.set_hl(ns1, 'LinkGroup', {fg = 'Blue', default=true}) - meths.set_hl_ns(ns1) + meths._set_hl_ns(ns1) feed 'k' screen:expect{grid=[[ @@ -301,4 +303,273 @@ describe('decorations providers', function() | ]]} end) + + it('can have virtual text', function() + insert(mulholland) + setup_provider [[ + local hl = a.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = a.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + a.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'+', 'ErrorMsg'}}; + virt_text_pos='overlay'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + {2:+}/ just to see if there was an accident | + {2:+}/ on Mulholland Drive | + {2:+}ry_start(); | + {2:+}ufref_T save_buf; | + {2:+}witch_buffer(&save_buf, buf); | + {2:+}osp = getmark(mark, false); | + {2:+}estore_buffer(&save_buf);^ | + | + ]]} + end) + + it('can highlight beyond EOL', function() + insert(mulholland) + setup_provider [[ + local test_ns = a.nvim_create_namespace "veberod" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + if string.find(a.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then + a.nvim_buf_set_extmark(buf, test_ns, line, 0, { + end_line = line+1; + hl_group = 'DiffAdd'; + hl_eol = true; + ephemeral = true; + }) + end + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); | + {13:bufref_T save_buf; }| + {13:switch_buffer(&save_buf, buf); }| + posp = getmark(mark, false); | + {13:restore_buffer(&save_buf);^ }| + | + ]]} + end) +end) + +describe('extmark decorations', function() + local screen + before_each( function() + clear() + screen = Screen.new(50, 15) + screen:attach() + screen:set_default_attr_ids { + [1] = {bold=true, foreground=Screen.colors.Blue}; + [2] = {foreground = Screen.colors.Brown}; + [3] = {bold = true, foreground = Screen.colors.SeaGreen}; + [4] = {background = Screen.colors.Red1, foreground = Screen.colors.Gray100}; + [5] = {foreground = Screen.colors.Brown, bold = true}; + [6] = {foreground = Screen.colors.DarkCyan}; + [7] = {foreground = Screen.colors.Grey0, background = tonumber('0xff4c4c')}; + [8] = {foreground = tonumber('0x180606'), background = tonumber('0xff4c4c')}; + [9] = {foreground = tonumber('0xe40c0c'), background = tonumber('0xff4c4c'), bold = true}; + [10] = {foreground = tonumber('0xb20000'), background = tonumber('0xff4c4c')}; + [11] = {blend = 30, background = Screen.colors.Red1}; + [12] = {foreground = Screen.colors.Brown, blend = 30, background = Screen.colors.Red1, bold = true}; + [13] = {foreground = Screen.colors.Fuchsia}; + [14] = {background = Screen.colors.Red1, foreground = Screen.colors.Black}; + [15] = {background = Screen.colors.Red1, foreground = tonumber('0xb20000')}; + [16] = {blend = 30, background = Screen.colors.Red1, foreground = Screen.colors.Magenta1}; + [17] = {bold = true, foreground = Screen.colors.Brown, background = Screen.colors.LightGrey}; + [18] = {background = Screen.colors.LightGrey}; + [19] = {foreground = Screen.colors.Cyan4, background = Screen.colors.LightGrey}; + [20] = {foreground = tonumber('0x180606'), background = tonumber('0xf13f3f')}; + [21] = {foreground = Screen.colors.Gray0, background = tonumber('0xf13f3f')}; + [22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')}; + [23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey}; + [24] = {bold = true}; + } + end) + + local example_text = [[ +for _,item in ipairs(items) do + local text, hl_id_cell, count = unpack(item) + if hl_id_cell ~= nil then + hl_id = hl_id_cell + end + for _ = 1, (count or 1) do + local cell = line[colpos] + cell.text = text + cell.hl_id = hl_id + colpos = colpos+1 + end +end]] + + it('can have virtual text of overlay position', function() + insert(example_text) + feed 'gg' + + local ns = meths.create_namespace 'test' + for i = 1,9 do + meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'}) + if i == 3 or (i >= 6 and i <= 9) then + meths.buf_set_extmark(0, ns, i, 4, { virt_text={{'|', 'NonText'}}, virt_text_pos='overlay'}) + end + end + meths.buf_set_extmark(0, ns, 9, 10, { virt_text={{'foo'}, {'bar', 'MoreMsg'}, {'!!', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + -- can "float" beyond end of line + meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) + -- bound check: right edge of window + meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + {2:|} local text, hl_id_cell, count = unpack(item) | + {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}| + {2:|} {1:|} hl_id = hl_id_cell | + {2:|} end | + {2:|} for _ = 1, (count or 1) {4:loopy} | + {2:|} {1:|} local cell = line[colpos] | + {2:|} {1:|} cell.text = text | + {2:|} {1:|} cell.hl_id = hl_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + + + -- handles broken lines + screen:try_resize(22, 25) + screen:expect{grid=[[ + ^for _,item in ipairs(i| + tems) do | + {2:|} local text, hl_id_| + cell, count = unpack(i| + tem) | + {2:|} if hl_id_cell ~= n| + il tbork bork bork {4:bor}| + {2:|} {1:|} hl_id = hl_id_| + cell | + {2:|} end | + {2:|} for _ = 1, (count | + or 1) {4:loopy} | + {2:|} {1:|} local cell = l| + ine[colpos] | + {2:|} {1:|} cell.text = te| + xt | + {2:|} {1:|} cell.hl_id = h| + l_id | + {2:|} {1:|} cofoo{3:bar}{4:!!}olpo| + s+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + end) + + it('can have virtual text of overlay position and styling', function() + insert(example_text) + feed 'gg' + local ns = meths.create_namespace 'test' + + command 'set ft=lua' + command 'syntax on' + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:local} text, hl_id_cell, count = unpack(item) | + {5:if} hl_id_cell ~= {13:nil} {5:then} | + hl_id = hl_id_cell | + {5:end} | + {5:for} _ = {13:1}, (count {5:or} {13:1}) {5:do} | + {5:local} cell = line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + command 'hi Blendy guibg=Red blend=30' + meths.buf_set_extmark(0, ns, 1, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 2, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine'}) + meths.buf_set_extmark(0, ns, 3, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace'}) + + meths.buf_set_extmark(0, ns, 4, 5, { virt_text={{'blendy text - here', 'Blendy'}}, virt_text_pos='overlay', hl_mode='blend', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 5, 5, { virt_text={{'combining color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='combine', virt_text_hide=true}) + meths.buf_set_extmark(0, ns, 6, 5, { virt_text={{'replacing color', 'Blendy'}}, virt_text_pos='overlay', hl_mode='replace', virt_text_hide=true}) + + screen:expect{grid=[[ + {5:^for} _,item {5:in} {6:ipairs}(items) {5:do} | + {5:l}{8:blen}{7:dy}{10:e}{7:text}{10:h}{7:-}{10:_}{7:here}ell, count = unpack(item) | + {5:i}{12:c}{11:ombining color} {13:nil} {5:then} | + {11:replacing color}d_cell | + {5:e}{8:bl}{14:endy}{15:i}{14:text}{15:o}{14:-}{15:o}{14:h}{7:ere} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + | + ]]} + + feed 'V5G' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{5:^e}{17:nd} | + {5:f}{12:co}{11:mbini}{16:n}{11:g color}t {5:or} {13:1}) {5:do} | + {11:replacing color} line[colpos] | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} + + feed 'jj' + screen:expect{grid=[[ + {17:for}{18: _,item }{17:in}{18: }{19:ipairs}{18:(items) }{17:do} | + {18: }{17:l}{20:blen}{21:dy}{22:e}{21:text}{22:h}{21:-}{22:_}{21:here}{18:ell, count = unpack(item)} | + {18: }{17:i}{12:c}{11:ombining color}{18: }{23:nil}{18: }{17:then} | + {18: }{11:replacing color}{18:d_cell} | + {18: }{17:end} | + {18: }{17:for}{18: _ = }{23:1}{18:, (count }{17:or}{18: }{23:1}{18:) }{17:do} | + {18: }^ {18: }{17:local}{18: cell = line[colpos]} | + cell.text = text | + cell.hl_id = hl_id | + colpos = colpos+{13:1} | + {5:end} | + {5:end} | + {1:~ }| + {1:~ }| + {24:-- VISUAL LINE --} | + ]]} + end) end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 69b6ab8cf0..a8d9fb02fc 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -1049,6 +1049,8 @@ it('diff updates line numbers below filler lines', function() [9] = {background = Screen.colors.LightMagenta}, [10] = {bold = true, foreground = Screen.colors.Brown}, [11] = {foreground = Screen.colors.Brown}, + [12] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red}; + [13] = {background = Screen.colors.Gray90}; }) source([[ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) @@ -1107,4 +1109,22 @@ it('diff updates line numbers below filler lines', function() {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) + command("set signcolumn number tgc cursorline") + command("hi CursorLineNr guibg=red") + screen:expect{grid=[[ + {1: }a {3:│}{11: 2 }a | + {1: }a {3:│}{11: 1 }a | + {1: }a {3:│}{12:3 }{13:^a }| + {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }| + {1: }{4:x }{3:│}{11: }{2:----------------}| + {1: }{4:x }{3:│}{11: }{2:----------------}| + {1: }b {3:│}{11: 2 }b | + {1: }b {3:│}{11: 3 }b | + {1: }b {3:│}{11: 4 }b | + {1: }b {3:│}{11: 5 }b | + {1: }b {3:│}{11: 6 }b | + {6:~ }{3:│}{6:~ }| + {3:[No Name] [+] }{7:[No Name] [+] }| + signcolumn=auto | + ]]} end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 32f9ae030f..3e73d8b3de 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -14,7 +14,7 @@ local funcs = helpers.funcs local run = helpers.run local pcall_err = helpers.pcall_err -describe('floatwin', function() +describe('float window', function() before_each(function() clear() end) @@ -42,6 +42,10 @@ describe('floatwin', function() [20] = {bold = true, foreground = Screen.colors.Brown}, [21] = {background = Screen.colors.Gray90}, [22] = {background = Screen.colors.LightRed}, + [23] = {foreground = Screen.colors.Black, background = Screen.colors.White}; + [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; + [25] = {blend = 100, background = Screen.colors.Gray0}; + [26] = {blend = 80, background = Screen.colors.Gray0}; } it('behavior', function() @@ -131,7 +135,7 @@ describe('floatwin', function() local screen before_each(function() screen = Screen.new(40,7) - screen:attach({ext_multigrid=multigrid}) + screen:attach {ext_multigrid=multigrid} screen:set_default_attr_ids(attrs) end) @@ -595,6 +599,437 @@ describe('floatwin', function() end end) + it('can have border', function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', + ' BORDAA '}) + local win = meths.open_win(buf, false, {relative='editor', width=9, height=2, row=2, col=5, border="double"}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {border="single"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:┌─────────┐}| + {5:│}{1: halloj! }{5:│}| + {5:│}{1: BORDAA }{5:│}| + {5:└─────────┘}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:┌─────────┐}{0: }| + {0:~ }{5:│}{1: halloj! }{5:│}{0: }| + {0:~ }{5:│}{1: BORDAA }{5:│}{0: }| + {0:~ }{5:└─────────┘}{0: }| + | + ]]} + end + + meths.win_set_config(win, {border="solid"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5: }| + {5: }{1: halloj! }{5: }| + {5: }{1: BORDAA }{5: }| + {5: }| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5: }{0: }| + {0:~ }{5: }{1: halloj! }{5: }{0: }| + {0:~ }{5: }{1: BORDAA }{5: }{0: }| + {0:~ }{5: }{0: }| + | + ]]} + end + + -- support: ascii char, UTF-8 char, composed char, highlight per char + meths.win_set_config(win, {border={"x", {"å", "ErrorMsg"}, {"\\"}, {"n̈̊", "Search"}}}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:x}{7:ååååååååå}{5:\}| + {17:n̈̊}{1: halloj! }{17:n̈̊}| + {17:n̈̊}{1: BORDAA }{17:n̈̊}| + {5:\}{7:ååååååååå}{5:x}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:x}{7:ååååååååå}{5:\}{0: }| + {0:~ }{17:n̈̊}{1: halloj! }{17:n̈̊}{0: }| + {0:~ }{17:n̈̊}{1: BORDAA }{17:n̈̊}{0: }| + {0:~ }{5:\}{7:ååååååååå}{5:x}{0: }| + | + ]]} + end + + meths.win_set_config(win, {border="none"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1: halloj! }| + {1: BORDAA }| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{1: halloj! }{0: }| + {0:~ }{1: BORDAA }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end + + meths.win_set_config(win, {border={"", "", "", ">", "", "", "", "<"}}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:<}{1: halloj! }{5:>}| + {5:<}{1: BORDAA }{5:>}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:<}{1: halloj! }{5:>}{0: }| + {0:~ }{5:<}{1: BORDAA }{5:>}{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end + + insert [[ + neeed some dummy + background text + to show the effect + of color blending + of border shadow + ]] + + meths.win_set_config(win, {border="shadow"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + neeed some dummy | + background text | + to show the effect | + of color blending | + of border shadow | + ^ | + ## grid 3 + | + ## grid 5 + {1: halloj! }{25: }| + {1: BORDAA }{26: }| + {25: }{26: }| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 6, curline = 5, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + neeed some dummy | + background text | + to {1: halloj! }{23:e}ffect | + of {1: BORDAA }{24:n}ding | + of {23:b}{24:order sha}dow | + ^ | + | + ]]} + end + end) + + it('with border show popupmenu', function() + screen:try_resize(40,10) + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'aaa aab ', + 'abb acc ', ''}) + meths.open_win(buf, true, {relative='editor', width=9, height=3, row=0, col=5, border="double"}) + feed 'G' + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1:aaa aab }{5:║}| + {5:║}{1:abb acc }{5:║}| + {5:║}{1:^ }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 0, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 0}; + }} + else + screen:expect{grid=[[ + {5:╔═════════╗} | + {0:~ }{5:║}{1:aaa aab }{5:║}{0: }| + {0:~ }{5:║}{1:abb acc }{5:║}{0: }| + {0:~ }{5:║}{1:^ }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end + + feed 'i<c-x><c-p>' + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- }{8:match 1 of 4} | + ## grid 5 + {5:╔═════════╗}| + {5:║}{1:aaa aab }{5:║}| + {5:║}{1:abb acc }{5:║}| + {5:║}{1:acc^ }{5:║}| + {5:╚═════════╝}| + ## grid 6 + {1: aaa }| + {1: aab }| + {1: abb }| + {13: acc }| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 0, 5, true }, + [6] = { { id = -1 }, "NW", 5, 4, 0, false } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 3}; + }} + else + screen:expect{grid=[[ + {5:╔═════════╗} | + {0:~ }{5:║}{1:aaa aab }{5:║}{0: }| + {0:~ }{5:║}{1:abb acc }{5:║}{0: }| + {0:~ }{5:║}{1:acc^ }{5:║}{0: }| + {0:~ }{1: aaa }{0: }| + {0:~ }{1: aab }{0: }| + {0:~ }{1: abb }{0: }| + {0:~ }{13: acc }{0: }| + {0:~ }| + {3:-- }{8:match 1 of 4} | + ]]} + end + end) + it('can have minimum size', function() insert("the background text") local buf = meths.create_buf(false, true) @@ -5433,6 +5868,155 @@ describe('floatwin', function() ]]) end end) + + it("correctly redraws when overlaid windows are resized #13991", function() + helpers.source([[ + let popup_config = {"relative" : "editor", + \ "width" : 7, + \ "height" : 3, + \ "row" : 1, + \ "col" : 1, + \ "style" : "minimal"} + + let border_config = {"relative" : "editor", + \ "width" : 9, + \ "height" : 5, + \ "row" : 0, + \ "col" : 0, + \ "style" : "minimal"} + + let popup_buffer = nvim_create_buf(v:false, v:true) + let border_buffer = nvim_create_buf(v:false, v:true) + let popup_win = nvim_open_win(popup_buffer, v:true, popup_config) + let border_win = nvim_open_win(border_buffer, v:false, border_config) + + call nvim_buf_set_lines(popup_buffer, 0, -1, v:true, + \ ["long", "longer", "longest"]) + + call nvim_buf_set_lines(border_buffer, 0, -1, v:true, + \ ["---------", "- -", "- -"]) + ]]) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 5 + {2:^long }| + {2:longer }| + {2:longest}| + ## grid 6 + {2:---------}| + {2:- -}| + {2:- -}| + {2: }| + {2: }| + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue1, bold = true}; + [2] = {background = Screen.colors.LightMagenta}; + }, float_pos={ + [5] = { { + id = 1002 + }, "NW", 1, 1, 1, true }, + [6] = { { + id = 1003 + }, "NW", 1, 0, 0, true } + }} + else + screen:expect([[ + {1:---------} | + {1:-^long -}{0: }| + {1:-longer -}{0: }| + {1: longest }{0: }| + {1: }{0: }| + {0:~ }| + | + ]]) + end + + helpers.source([[ + let new_popup_config = {"width" : 1, "height" : 3} + let new_border_config = {"width" : 3, "height" : 5} + + function! Resize() + call nvim_win_set_config(g:popup_win, g:new_popup_config) + call nvim_win_set_config(g:border_win, g:new_border_config) + + call nvim_buf_set_lines(g:border_buffer, 0, -1, v:true, + \ ["---", "- -", "- -"]) + endfunction + + nnoremap zz <cmd>call Resize()<cr> + ]]) + + helpers.feed("zz") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 5 + {2:^l}| + {2:o}| + {2:n}| + ## grid 6 + {2:---}| + {2:- -}| + {2:- -}| + {2: }| + {2: }| + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue1, bold = true}; + [2] = {background = Screen.colors.LightMagenta}; + }, float_pos={ + [5] = { { + id = 1002 + }, "NW", 1, 1, 1, true }, + [6] = { { + id = 1003 + }, "NW", 1, 0, 0, true } + }} + else + screen:expect([[ + {1:---} | + {1:-^l-}{0: }| + {1:-o-}{0: }| + {1: n }{0: }| + {1: }{0: }| + {0:~ }| + | + ]]) + end + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 6ce8b33a63..8883ad8270 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -38,7 +38,9 @@ describe("folded lines", function() [6] = {background = Screen.colors.Yellow}, [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, [8] = {foreground = Screen.colors.Brown }, - [9] = {bold = true, foreground = Screen.colors.Brown} + [9] = {bold = true, foreground = Screen.colors.Brown}, + [10] = {background = Screen.colors.LightGrey, underline = true}, + [11] = {bold=true}, }) end) @@ -290,6 +292,569 @@ describe("folded lines", function() end end) + it("works with split", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + feed('zO') + feed_command("rightbelow new") + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + if multigrid then + meths.input_mouse('left', 'press', '', 4, 0, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + {2:[No Name] [+] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:+}{5:+--- 4 lines: bb···························}| + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 3, 0) + screen:expect([[ + {7:-}aa | + {7:-}bb | + {2:[No Name] [+] }| + {7:-}^aa | + {7:+}{5:+--- 4 lines: bb···························}| + {7:│}ff | + {3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + {2:[No Name] [+] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:-}bb | + {7:2}cc | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 4, 0) + screen:expect([[ + {7:-}aa | + {7:-}bb | + {2:[No Name] [+] }| + {7:-}^aa | + {7:-}bb | + {7:2}cc | + {3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + {3:[No Name] [+] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:+}{5:^+--- 4 lines: bb···························}| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {7:-}aa | + {7:+}{5:^+--- 4 lines: bb···························}| + {3:[No Name] [+] }| + {7:-}aa | + {7:-}bb | + {7:2}cc | + {2:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + {3:[No Name] [+] }| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + {2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 6 lines: aa····························}| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + {7:+}{5:^+-- 6 lines: aa····························}| + {1:~ }| + {3:[No Name] [+] }| + {7:-}aa | + {7:-}bb | + {7:2}cc | + {2:[No Name] [+] }| + :1 | + ]]) + end + end) + + it("works with vsplit", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + feed('zO') + feed_command("rightbelow vnew") + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=1") + feed('zf3j') + feed_command('1') + feed('zf2j') + if multigrid then + meths.input_mouse('left', 'press', '', 4, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:+}{5:+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:+}{5:+--- 4 lines: bb····}| + {7:2}cc {2:│}{7:│}ff | + {7:2}dd {2:│}{1:~ }| + {7:2}ee {2:│}{1:~ }| + {7:│}ff {2:│}{1:~ }| + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {2:[No Name] [+] }{3:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ## grid 3 + :1 | + ## grid 4 + {7:-}^aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 23) + screen:expect([[ + {7:-}aa {2:│}{7:-}^aa | + {7:-}bb {2:│}{7:-}bb | + {7:2}cc {2:│}{7:2}cc | + {7:2}dd {2:│}{7:2}dd | + {7:2}ee {2:│}{7:2}ee | + {7:│}ff {2:│}{7:│}ff | + {2:[No Name] [+] }{3:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}| + {7:│}ff | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {7:-}aa {2:│}{7:-}aa | + {7:+}{5:^+--- 4 lines: bb····}{2:│}{7:-}bb | + {7:│}ff {2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + [2:----------------------]{2:│}[4:----------------------]| + {3:[No Name] [+] }{2:[No Name] [+] }| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 6 lines: aa·····}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :1 | + ## grid 4 + {7:-}aa | + {7:-}bb | + {7:2}cc | + {7:2}dd | + {7:2}ee | + {7:│}ff | + ]]) + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + {7:+}{5:^+-- 6 lines: aa·····}{2:│}{7:-}aa | + {1:~ }{2:│}{7:-}bb | + {1:~ }{2:│}{7:2}cc | + {1:~ }{2:│}{7:2}dd | + {1:~ }{2:│}{7:2}ee | + {1:~ }{2:│}{7:│}ff | + {3:[No Name] [+] }{2:[No Name] [+] }| + :1 | + ]]) + end + end) + + it("works with tab", function() + insert([[ + aa + bb + cc + dd + ee + ff]]) + feed_command('2') + command("set foldcolumn=2") + feed('zf3j') + feed_command('1') + feed('zf2j') + feed('zO') + feed_command("tab split") + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 1) + screen:expect([[ + ## grid 1 + {10: + [No Name] }{11: + [No Name] }{2: }{10:X}| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 (hidden) + {7:- }aa | + {7:│-}bb | + {7:││}cc | + {7:││}dd | + {7:││}ee | + {7:│ }ff | + {1:~ }| + ## grid 3 + :tab split | + ## grid 4 + {7:- }^aa | + {7:│+}{5:+--- 4 lines: bb··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 2, 1) + screen:expect([[ + {10: + [No Name] }{11: + [No Name] }{2: }{10:X}| + {7:- }^aa | + {7:│+}{5:+--- 4 lines: bb··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + :tab split | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 0, 0) + screen:expect([[ + ## grid 1 + {10: + [No Name] }{11: + [No Name] }{2: }{10:X}| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [4:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 (hidden) + {7:- }aa | + {7:│-}bb | + {7:││}cc | + {7:││}dd | + {7:││}ee | + {7:│ }ff | + {1:~ }| + ## grid 3 + :tab split | + ## grid 4 + {7:+ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {10: + [No Name] }{11: + [No Name] }{2: }{10:X}| + {7:+ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :tab split | + ]]) + end + + feed_command("tabnext") + if multigrid then + meths.input_mouse('left', 'press', '', 2, 1, 1) + screen:expect([[ + ## grid 1 + {11: + [No Name] }{10: + [No Name] }{2: }{10:X}| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:- }^aa | + {7:│+}{5:+--- 4 lines: bb··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :tabnext | + ## grid 4 (hidden) + {7:+ }{5:+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 2, 1) + screen:expect([[ + {11: + [No Name] }{10: + [No Name] }{2: }{10:X}| + {7:- }^aa | + {7:│+}{5:+--- 4 lines: bb··························}| + {7:│ }ff | + {1:~ }| + {1:~ }| + {1:~ }| + :tabnext | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 2, 0, 0) + screen:expect([[ + ## grid 1 + {11: + [No Name] }{10: + [No Name] }{2: }{10:X}| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :tabnext | + ## grid 4 (hidden) + {7:+ }{5:+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + else + meths.input_mouse('left', 'press', '', 0, 1, 0) + screen:expect([[ + {11: + [No Name] }{10: + [No Name] }{2: }{10:X}| + {7:+ }{5:^+-- 6 lines: aa···························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :tabnext | + ]]) + end + end) + it("works with multibyte text", function() -- Currently the only allowed value of 'maxcombine' eq(6, meths.get_option('maxcombine')) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index ef3acd7d2e..8992ee27ce 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -333,10 +333,10 @@ describe('highlight defaults', function() command('highlight clear EndOfBuffer') screen:expect{grid=[[ ^ | - ~ | - ~ | + {1:~ }| + {1:~ }| | - ]], hl_groups={EndOfBuffer=0, MsgSeparator=2}} + ]], hl_groups={EndOfBuffer=1, MsgSeparator=2}} end) end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 9313a35708..ea8968a653 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,16 +1,18 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim +local clear, feed_command = helpers.clear, helpers.feed_command local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq local command = helpers.command local expect = helpers.expect +local meths = helpers.meths +local exec_lua = helpers.exec_lua local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') -describe('mappings', function() - local cid +before_each(clear) +describe('mappings', function() local add_mapping = function(mapping, send) - local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '" + local cmd = "nnoremap "..mapping.." :call rpcnotify(1, 'mapped', '" ..send:gsub('<', '<lt>').."')<cr>" feed_command(cmd) end @@ -21,8 +23,6 @@ describe('mappings', function() end before_each(function() - clear() - cid = nvim('get_api_info')[1] add_mapping('<C-L>', '<C-L>') add_mapping('<C-S-L>', '<C-S-L>') add_mapping('<s-up>', '<s-up>') @@ -115,7 +115,6 @@ describe('mappings', function() end) describe('input utf sequences that contain CSI/K_SPECIAL', function() - before_each(clear) it('ok', function() feed('i…<esc>') expect('…') @@ -129,7 +128,6 @@ describe('input non-printable chars', function() it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) - clear() local screen = Screen.new(60,8) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, @@ -215,3 +213,27 @@ describe('input non-printable chars', function() ]]) end) end) + +describe("event processing and input", function() + it('not blocked by event bursts', function() + meths.set_keymap('', '<f2>', "<cmd>lua vim.rpcnotify(1, 'stop') winning = true <cr>", {noremap=true}) + + exec_lua [[ + winning = false + burst = vim.schedule_wrap(function(tell) + if tell then + vim.rpcnotify(1, 'start') + end + -- Are we winning, son? + if not winning then + burst(false) + end + end) + burst(true) + ]] + + eq({'notification', 'start', {}}, next_msg()) + feed '<f2>' + eq({'notification', 'stop', {}}, next_msg()) + end) +end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 5df4a1d533..9d7719a7c0 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -29,6 +29,7 @@ describe('ui/ext_messages', function() [6] = {bold = true, reverse = true}, [7] = {background = Screen.colors.Yellow}, [8] = {foreground = Screen.colors.Red}, + [9] = {special = Screen.colors.Red, undercurl = true}, }) end) after_each(function() @@ -455,6 +456,8 @@ describe('ui/ext_messages', function() {1:~ }| ]], messages={ {kind="echomsg", content={{"stuff"}}}, + }, showmode={ + { "-- INSERT --", 3 } }} end) @@ -795,6 +798,45 @@ describe('ui/ext_messages', function() pos = 9, }}} end) + + it('hides prompt_for_number messages', function() + command('set spell') + feed('ihelllo<esc>') + + feed('z=') + screen:expect{grid=[[ + {9:helllo} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:^~ }| + ]], messages={ + {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with mouse (empty cancels): ' } }, kind = ""} + }} + + feed('1') + screen:expect{grid=[[ + {9:helllo} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:^~ }| + ]], messages={ + {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with mouse (empty cancels): ' } }, kind = ""}, + { content = { { "1" } }, kind = "" } + }} + + feed('<cr>') + screen:expect{grid=[[ + ^Hello | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + + end) + end) describe('ui/builtin messages', function() diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index ff9f30d0a1..958e137f65 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -24,10 +24,6 @@ describe('screen', function() } ) end) - after_each(function() - screen:detach() - end) - it('default initial screen', function() screen:expect([[ ^ | @@ -67,10 +63,6 @@ local function screen_tests(linegrid) } ) end) - after_each(function() - screen:detach() - end) - describe(':suspend', function() it('is forwarded to the UI', function() local function check() @@ -1004,3 +996,39 @@ describe('Screen default colors', function() end} end) end) + + +describe('screen with msgsep deactivated on startup', function() + local screen + + before_each(function() + clear('--cmd', 'set display-=msgsep') + screen = Screen.new() + screen:attach() + screen:set_default_attr_ids { + [0] = {bold=true, foreground=255}; + [7] = {bold = true, foreground = Screen.colors.SeaGreen}; + } + end) + + it('execute command with multi-line output', function() + feed ':ls<cr>' + screen:expect([[ + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed '<cr>' -- skip the "Press ENTER..." state or tests will hang + end) +end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 656f613c6a..5540b3c2dc 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -507,7 +507,21 @@ describe('search highlighting', function() {1:~ }| :syntax keyword MyGroup special | ]]) + end) + it('highlights entire pattern on :%g@a/b', function() + command('set inccommand=nosplit') + feed('ia/b/c<Esc>') + feed(':%g@a/b') + screen:expect([[ + {3:a/b}/c | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :%g@a/b^ | + ]]) end) end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 1937102782..06c92a4b10 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -264,6 +264,24 @@ describe('Signs', function() {0:~ }| | ]]} + -- line deletion deletes signs. + command('2d') + screen:expect([[ + {1:>>}XX{2: }{6: 1 }a | + XX{1:>>}WW{6: 2 }^c | + {2: }{6: 3 } | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) end) it('auto-resize sign column with minimum size (#13783)', function() 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/functional/viml/errorlist_spec.lua b/test/functional/viml/errorlist_spec.lua index 9acc61e398..077d816903 100644 --- a/test/functional/viml/errorlist_spec.lua +++ b/test/functional/viml/errorlist_spec.lua @@ -68,4 +68,17 @@ describe('setloclist()', function() command('lclose | wincmd w | lopen') eq('foo', get_cur_win_var('quickfix_title')) end) + + it("doesn't crash when when window is closed in the middle #13721", function() + helpers.insert([[ + hello world]]) + + command("vsplit") + command("autocmd WinLeave * :call nvim_win_close(0, v:true)") + + command("call setloclist(0, [])") + command("lopen") + + helpers.assert_alive() + end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 8dbd82cb8c..12d9f19187 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -365,7 +365,11 @@ function module.check_cores(app, force) db_cmd = lldb_db_cmd else initial_path = '.' - re = '/core[^/]*$' + if 'freebsd' == module.uname() then + re = '/nvim.core$' + else + re = '/core[^/]*$' + end exc_re = { '^/%.deps$', '^/%'..deps_prefix()..'$', local_tmpdir, '^/%node_modules$' } db_cmd = gdb_db_cmd random_skip = true 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 aab8eb4464..d0e7cdc9e3 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -147,8 +147,8 @@ set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0 set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/1d8b747c161db457e032a023ebbff511f5de5ec2.tar.gz) -set(LUAJIT_SHA256 20a159c38a98ecdb6368e8d655343b6036622a29a1621da9dc303f7ed9bf37f3) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/787736990ac3b7d5ceaba2697c7d0f58f77bb782.tar.gz) +set(LUAJIT_SHA256 2e3f74bc279f46cc463abfc67b36e69faaf0366237004771f4cac4bf2a9f5efb) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) @@ -196,11 +196,12 @@ 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/99151b1.tar.gz) -set(TREESITTER_C_SHA256 950386f9ba77fb6a7e992198d4f219c34238a2bbc005c5f53c4212d0f8772b06) +set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/5aa0bbb.tar.gz) +set(TREESITTER_C_SHA256 a5dcb37460d83002dfae7f9a208170ddbc9a047f231b9d6b75da7d36d707db2f) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/0.18.0.zip) -set(TREESITTER_SHA256 ac53b7708ca47161dac7f8e852bd61accb8527d45b7ad72e29e12e8e72dbe440) +# This is 0.19.4+2, fixes some segfaults https://github.com/tree-sitter/tree-sitter/commit/89e1157a299596f3ce2155ba9fd69d5e2c03d3e6 +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/89e1157a299596f3ce2155ba9fd69d5e2c03d3e6.zip) +set(TREESITTER_SHA256 d5be9fd92cbf783680f921b2adccbd721b9aa8b2c445114a216b6544330f252c) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |