diff options
285 files changed, 16421 insertions, 10226 deletions
diff --git a/.clang-tidy b/.clang-tidy index 040ceea843..90d12602d1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,55 +2,48 @@ Checks: > -*, + bugprone-*, - cert-env33-c, - cert-err34-c, - cert-flp30-c, - cert-msc30-c, - clang-analyzer-*, - clang-diagnostics-*, google-*, misc-*, - misc-non-copyable-objects, - misc-static-assert, + modernize-*, performance-*, portability-*, readability-*, + -bugprone-assignment-in-if-condition, -bugprone-branch-clone, -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-macro-parentheses, + -bugprone-misplaced-widening-cast, -bugprone-narrowing-conversions, + -bugprone-narrowing-conversions, + -bugprone-not-null-terminated-result, + -bugprone-reserved-identifier, -bugprone-sizeof-expression, + -bugprone-suspicious-include, + -bugprone-suspicious-memory-comparison, -bugprone-unused-return-value, - -clang-analyzer-security.insecureAPI.strcpy, + -google-readability-braces-around-statements, -google-readability-function-size, + -misc-misplaced-const, -misc-no-recursion, -misc-unused-parameters, + -modernize-macro-to-enum, -performance-no-int-to-ptr, + -readability-braces-around-statements, -readability-else-after-return, - -readability-function-*, + -readability-function-cognitive-complexity, + -readability-function-size, -readability-identifier-length, -readability-isolate-declaration, -readability-magic-numbers, + -readability-misleading-indentation, + -readability-redundant-declaration, -readability-redundant-function-ptr-dereference, -readability-suspicious-call-argument, -CheckOptions: - - key: readability-identifier-naming.FunctionCase - value: lower_case - - key: readability-identifier-naming.GlobalConstantCase - value: CamelCase - - key: readability-identifier-naming.GlobalConstantPrefix - value: k - - key: readability-identifier-naming.StaticConstantCase - value: CamelCase - - key: readability-identifier-naming.StaticConstantPrefix - value: k - - key: readability-identifier-naming.StructCase - value: CamelCase - - key: readability-identifier-naming.VariableCase - value: lower_case - WarningsAsErrors: '' HeaderFilterRegex: "(src/[cjson|klib|mpack|unicode|xdiff]/).*\\.h$" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index b9830dd2fd..4dba469ebe 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -53,10 +53,29 @@ aa4f9c5341f5280f16cce0630ea54b84eef717b3 93f24403f8cc760ff47979c596976b53a8b16358 1ffd527c837fb2465c9659273bbe5447a1352db2 2498e9feb025361576603a0101c86393d211e31e +0b3ae64480ea28bb57783c2269a61f0a60ffc55e +0fc8597f011e0927e529abd11bf0ddd8d0d1eaab +6ff245732a5a8ab821598a38fb0c5805e6bd3779 +abf758a2977c4e6cab4dfa217f56da853d85851c +cb84f5ee530f0f32b92bed5b4ad41344e8b551aa # typos d238b8f6003d34cae7f65ff7585b48a2cd9449fb 4547137aaff32b20172870a549d3a28a3c7adf1c +08616571f47cc367a5fe59b52295708b9fda3b09 +09c6ce8c4e4c6415cca9b834539ed0df461373f6 +0b0c4f7dfa4a9a564cbf44262d4bea9bdefe2dc9 +4a96e7809f4d9f6ce21869817eb95ff6dcaa1693 +61205c1defb64ac5466496b5451e4a7f3171e21e +64116d78502e0ca611e13adf9323ef2d3fe708c2 +abc087f4c65ca547cae58518b42aee82ff4a07f6 +b8dcbcc732baf84fc48d6b272c3ade0bcb129b3b +c815aadfccd6bada47ecfb09fe188ee7f7c5caf3 +caa6992a1071a2ac373bec21085685da4a1790d6 +df646572c53f55268a5dbb61628d7c3b302d5663 +e63e5d1dbd3dd4711efa0ecf9e844ff308b370a6 +eb123b565e201418dd135d2602dc20eea3cead39 +ff20d40321399fa187bd350f9619cf6418d7eb6e # generated docs ea333badd24f691c753d8048f911d1db349bc2cd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ada4f3f9e..732e3bb8c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,6 @@ jobs: automake \ build-essential \ cmake \ - flake8 \ gettext \ libluajit-5.1-dev \ libmsgpack-dev \ @@ -96,30 +95,32 @@ jobs: run: ./ci/before_script.sh - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintstylua uses: JohnnyMorganz/stylua-action@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + version: latest args: --check runtime/ - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintlua run: make lintlua - - if: "!cancelled()" - name: lintpy - run: make lintpy - - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintsh run: make lintsh - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: uncrustify run: | ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]") - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: suggester / uncrustify uses: reviewdog/action-suggester@v1 with: @@ -127,7 +128,7 @@ jobs: tool_name: uncrustify cleanup: false - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: check uncrustify run: | git diff --color --exit-code @@ -196,10 +197,15 @@ jobs: run: ./ci/run_tests.sh build_nvim - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintc run: make lintc - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: check-single-includes run: make check-single-includes @@ -213,11 +219,11 @@ jobs: matrix: include: - flavor: asan - cc: clang-13 + cc: clang runner: ubuntu-22.04 os: linux - flavor: tsan - cc: clang-13 + cc: clang runner: ubuntu-22.04 os: linux - flavor: uchar @@ -274,14 +280,6 @@ jobs: exit 1 } - - name: Install new clang - if: matrix.flavor == 'asan' || matrix.flavor == 'tsan' - run: | - wget https://apt.llvm.org/llvm.sh - chmod a+x llvm.sh - sudo ./llvm.sh 13 - rm llvm.sh - - name: Install brew packages if: matrix.os == 'osx' run: | @@ -304,19 +302,24 @@ jobs: - name: Build run: ./ci/run_tests.sh build_nvim - - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && !cancelled() + - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT + + - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && (success() || failure() && steps.abort_job.outputs.status == 'success') name: Unittests run: ./ci/run_tests.sh unittests - - if: matrix.flavor != 'tsan' && !cancelled() + - if: matrix.flavor != 'tsan' && (success() || failure() && steps.abort_job.outputs.status == 'success') name: Functionaltests run: ./ci/run_tests.sh functionaltests - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Oldtests run: ./ci/run_tests.sh oldtests - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Install nvim run: ./ci/run_tests.sh install_nvim @@ -327,9 +330,8 @@ jobs: runs-on: windows-2019 timeout-minutes: 45 env: - DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }} - DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }} - CMAKE_BUILD_TYPE: "RelWithDebInfo" + DEPS_BUILD_DIR: ${{ github.workspace }}/nvim-deps + DEPS_PREFIX: ${{ github.workspace }}/nvim-deps/usr name: windows (MSVC_64) steps: - uses: actions/checkout@v3 @@ -337,7 +339,7 @@ jobs: - uses: actions/cache@v3 with: path: ${{ env.DEPS_BUILD_DIR }} - key: ${{ hashFiles('cmake.deps\**') }} + key: windows-${{ hashFiles('cmake.deps/**', 'ci/build.ps1') }} - name: Build deps run: .\ci\build.ps1 -BuildDeps @@ -346,13 +348,18 @@ jobs: run: .\ci\build.ps1 -Build - name: Install test deps - continue-on-error: false run: .\ci\build.ps1 -EnsureTestDeps - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: | + "status=${{ job.status }}" >> $env:GITHUB_OUTPUT + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Run tests run: .\ci\build.ps1 -Test - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Run old tests run: .\ci\build.ps1 -TestOld diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d0f04553f2..6ba0d43fe0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp', 'python' ] + language: [ 'cpp' ] steps: - name: Checkout repository diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index da70d358a9..4b4df39ec0 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -30,7 +30,7 @@ case "$FLAVOR" in BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON" cat <<EOF >> "$GITHUB_ENV" CLANG_SANITIZER=ASAN_UBSAN -SYMBOLIZER=asan_symbolize-13 +SYMBOLIZER=asan_symbolize ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan:intercept_tls_get_addr=0 UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan EOF diff --git a/.github/workflows/vim-patches.yml b/.github/workflows/vim-patches.yml index df8c8116b5..c26f8d62e5 100644 --- a/.github/workflows/vim-patches.yml +++ b/.github/workflows/vim-patches.yml @@ -40,7 +40,7 @@ jobs: id: update-version run: | git checkout -b ${VERSION_BRANCH} - nvim -i NONE -u NONE --headless +'luafile scripts/vimpatch.lua' +q + nvim -V1 -es -i NONE +'luafile scripts/vimpatch.lua' +q printf '::set-output name=NEW_PATCHES::%s\n' $([ -z "$(git diff)" ]; echo $?) - name: Automatic PR diff --git a/.gitignore b/.gitignore index be43c5cd90..97fed7b949 100644 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,4 @@ tags # vim patches /vim-*.patch -/CMakeUserPresets.json +CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 34eed2826b..e721402613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -608,7 +608,6 @@ endif() # find_program(LUACHECK_PRG luacheck) find_program(STYLUA_PRG stylua) -find_program(FLAKE8_PRG flake8) find_program(UNCRUSTIFY_PRG uncrustify) find_program(SHELLCHECK_PRG shellcheck) @@ -635,14 +634,6 @@ add_custom_target(lintlua) add_dependencies(lintlua lintlua-luacheck lintlua-stylua) include(InstallHelpers) -add_glob_targets( - TARGET lintpy - COMMAND ${FLAKE8_PRG} - FLAGS --max-line-length 100 - GLOB_DIRS contrib scripts src test - GLOB_PAT *.py - TOUCH_STRATEGY SINGLE - ) add_glob_targets( TARGET lintsh @@ -658,7 +649,7 @@ add_custom_target(lintcommit add_dependencies(lintcommit nvim) add_custom_target(lint) -add_dependencies(lint check-single-includes lintc lintlua lintpy lintsh lintcommit lintuncrustify) +add_dependencies(lint check-single-includes lintc lintlua lintsh lintcommit lintuncrustify) # # Format diff --git a/MAINTAIN.md b/MAINTAIN.md index d65337f92b..a587425f57 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -98,6 +98,36 @@ These dependencies are "vendored" (inlined), we must update the sources manually We may maintain forks, if we are waiting on upstream changes: https://github.com/neovim/neovim/wiki/Deps +CI +-------------- + +### General + +As our CI is primarily dependent on GitHub Actions at the moment, then so will +our CI strategy be. The following guidelines have worked well for us so far: + +* Never use a macOS runner if an Ubuntu or a Windows runner can be used + instead. This is because macOS runners have a [tighter restrictions on the + number of concurrent jobs](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits). + +### Runner versions + +* For special-purpose jobs where the runner version doesn't really matter, + prefer `-latest` tags so we don't need to manually bump the versions. An + example of a special-purpose workflow is `labeler.yml`. + +* For our testing jobs, which is currently only `ci.yml`, prefer to use the + latest stable (i.e. non-beta) version explicitly. Avoid using the `-latest` + tags here as it makes it difficult to determine from an unrelated PR if a + failure is due to the PR itself or due to GitHub bumping the `-latest` tag + without our knowledge. There's also a high risk that automatic bumping the CI + versions will fail due to manual work being required from experience. + +* For our release job, which is `release.yml`, prefer to use the oldest stable + (i.e. non-deprecated) versions available. The reason is that we're trying to + produce images that work in the broadest number of environments, and + therefore want to use older releases. + See also -------- @@ -130,7 +130,7 @@ src/nvim/testdir/%.vim: phony_force functionaltest functionaltest-lua unittest benchmark: | nvim $(BUILD_TOOL) -C build $@ -lintlua lintsh lintpy lintuncrustify lintc lintcfull check-single-includes generated-sources lintcommit lint formatc formatlua format: | build/.ran-cmake +lintlua lintsh lintuncrustify lintc lintcfull check-single-includes generated-sources lintcommit lint formatc formatlua format: | build/.ran-cmake $(CMAKE_PRG) --build build --target $@ test: functionaltest unittest @@ -166,4 +166,4 @@ $(DEPS_BUILD_DIR)/%: phony_force $(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@) endif -.PHONY: test lintlua lintpy lintsh functionaltest unittest lint lintc clean distclean nvim libnvim cmake deps install appimage checkprefix lintcommit formatc formatlua format +.PHONY: test lintlua lintsh functionaltest unittest lint lintc clean distclean nvim libnvim cmake deps install appimage checkprefix lintcommit formatc formatlua format diff --git a/ci/build.ps1 b/ci/build.ps1 index f6ad744fa1..2340c2273f 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -15,31 +15,12 @@ $ProgressPreference = 'SilentlyContinue' $projectDir = [System.IO.Path]::GetFullPath("$(Get-Location)") $buildDir = Join-Path -Path $projectDir -ChildPath "build" -# $env:CMAKE_BUILD_TYPE is ignored by cmake when not using ninja -$cmakeBuildType = $(if ($null -ne $env:CMAKE_BUILD_TYPE) {$env:CMAKE_BUILD_TYPE} else {'RelWithDebInfo'}); -$depsCmakeVars = @{ - CMAKE_BUILD_TYPE=$cmakeBuildType; -} -$nvimCmakeVars = @{ - CMAKE_BUILD_TYPE=$cmakeBuildType; - BUSTED_OUTPUT_TYPE = 'nvim'; - DEPS_PREFIX=$(if ($null -ne $env:DEPS_PREFIX) {$env:DEPS_PREFIX} else {".deps/usr"}); -} -if ($null -eq $env:DEPS_BUILD_DIR) { - $env:DEPS_BUILD_DIR = Join-Path -Path $projectDir -ChildPath ".deps" -} -$uploadToCodeCov = $false - function exitIfFailed() { if ($LastExitCode -ne 0) { exit $LastExitCode } } -function convertToCmakeArgs($vars) { - return $vars.GetEnumerator() | ForEach-Object { "-D$($_.Key)=$($_.Value)" } -} - $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd.bat")) { & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x64 -no_logo && set" | ForEach-Object { @@ -49,26 +30,13 @@ if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd. } function BuildDeps { - - if (Test-Path -PathType container $env:DEPS_BUILD_DIR) { - $cachedBuildTypeStr = $(Get-Content $env:DEPS_BUILD_DIR\CMakeCache.txt | Select-String -Pattern "CMAKE_BUILD_TYPE.*=($cmakeBuildType)") - if (-not $cachedBuildTypeStr) { - Write-Warning " unable to validate build type from cache dir ${env:DEPS_BUILD_DIR}" - } - } - - # we currently can't use ninja for cmake.deps, see #19405 - $depsCmakeGenerator = "Visual Studio 16 2019" - $depsCmakeGeneratorPlf = "x64" - cmake -S "$projectDir\cmake.deps" -B $env:DEPS_BUILD_DIR -G $depsCmakeGenerator -A $depsCmakeGeneratorPlf $(convertToCmakeArgs($depsCmakeVars)); exitIfFailed - - $depsCmakeNativeToolOptions= @('/verbosity:normal', '/m') - cmake --build $env:DEPS_BUILD_DIR --config $cmakeBuildType -- $depsCmakeNativeToolOptions; exitIfFailed + cmake -S "$projectDir\cmake.deps" -B $env:DEPS_BUILD_DIR -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo'; exitIfFailed + cmake --build $env:DEPS_BUILD_DIR; exitIfFailed } function Build { - cmake -S $projectDir -B $buildDir $(convertToCmakeArgs($nvimCmakeVars)) -G Ninja; exitIfFailed - cmake --build $buildDir --config $cmakeBuildType; exitIfFailed + cmake -S $projectDir -B $buildDir -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX"; exitIfFailed + cmake --build $buildDir; exitIfFailed } function EnsureTestDeps { @@ -90,10 +58,6 @@ function EnsureTestDeps { npm.cmd install -g neovim; exitIfFailed Get-Command -CommandType Application neovim-node-host.cmd; exitIfFailed npm.cmd link neovim - - if ($env:USE_LUACOV -eq 1) { - & $env:DEPS_PREFIX\luarocks\luarocks.bat install cluacov - } } function Test { @@ -110,14 +74,6 @@ function Test { if ($failed) { exit $LastExitCode } - - if (-not $uploadToCodecov) { - return - } - if ($env:USE_LUACOV -eq 1) { - & $env:DEPS_PREFIX\bin\luacov.bat - } - bash -l /c/projects/neovim/ci/common/submit_coverage.sh functionaltest } function TestOld { @@ -128,15 +84,10 @@ function TestOld { $env:PATH = "C:\msys64\usr\bin;$env:PATH" & "C:\msys64\mingw64\bin\mingw32-make.exe" -C $(Convert-Path $projectDir\src\nvim\testdir) VERBOSE=1; exitIfFailed $env:PATH = $OldPath - - if ($uploadToCodecov) { - bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest - } } - function Package { - cmake -S $projectDir -B $buildDir $(convertToCmakeArgs($nvimCmakeVars)) -G Ninja; exitIfFailed + cmake -S $projectDir -B $buildDir -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX"; exitIfFailed cmake --build $buildDir --target package; exitIfFailed } diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 7588291fbf..62c38e2a2f 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -197,14 +197,14 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz) set(TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 ) -set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.13.tar.gz) -set(TREESITTER_LUA_SHA256 564594fe0ffd2f2fb3578a15019b723e1bc94ac82cb6a0103a6b3b9ddcc6f315) +set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz) +set(TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d) set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/v0.2.0.tar.gz) set(TREESITTER_VIM_SHA256 608dcc31a7948cb66ae7f45494620e2e9face1af75598205541f80d782ec4501) -set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v1.2.3.tar.gz) -set(TREESITTER_HELP_SHA256 e0a0d1c6c142b0096bb9ca22acd7e7ad3b205fbe68c26e6bfbbe03632c04db08) +set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v1.2.5.tar.gz) +set(TREESITTER_HELP_SHA256 379d764937a0e3a38e3f9ce9a62c93ba24211a236c74181dd04ee3b4631472a8) set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.7.tar.gz) set(TREESITTER_SHA256 b355e968ec2d0241bbd96748e00a9038f83968f85d822ecb9940cbe4c42e182e) diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim deleted file mode 100644 index 9b387095ee..0000000000 --- a/runtime/autoload/health/nvim.vim +++ /dev/null @@ -1,284 +0,0 @@ -let s:suggest_faq = 'https://github.com/neovim/neovim/wiki/FAQ' - -function! s:check_config() abort - let ok = v:true - call health#report_start('Configuration') - - let vimrc = empty($MYVIMRC) ? stdpath('config').'/init.vim' : $MYVIMRC - if !filereadable(vimrc) - let ok = v:false - let has_vim = filereadable(expand('~/.vimrc')) - call health#report_warn((-1 == getfsize(vimrc) ? 'Missing' : 'Unreadable').' user config file: '.vimrc, - \[ has_vim ? ':help nvim-from-vim' : ':help init.vim' ]) - endif - - " If $VIM is empty we don't care. Else make sure it is valid. - if !empty($VIM) && !filereadable($VIM.'/runtime/doc/nvim.txt') - let ok = v:false - call health#report_error('$VIM is invalid: '.$VIM) - endif - - if exists('$NVIM_TUI_ENABLE_CURSOR_SHAPE') - let ok = v:false - call health#report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', - \ [ "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", - \ 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402' ]) - endif - - if v:ctype ==# 'C' - let ok = v:false - call health#report_error('Locale does not support UTF-8. Unicode characters may not display correctly.' - \ .printf("\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s", $LANG, $LC_ALL, $LC_CTYPE), - \ [ 'If using tmux, try the -u option.', - \ 'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.' , - \ 'Configure your system locale.' ]) - endif - - if &paste - let ok = v:false - call health#report_error("'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", - \ [ 'Remove `set paste` from your init.vim, if applicable.', - \ 'Check `:verbose set paste?` to see if a plugin or script set the option.', ]) - endif - - let writeable = v:true - let shadafile = empty(&shada) ? &shada : substitute(matchstr( - \ split(&shada, ',')[-1], '^n.\+'), '^n', '', '') - let shadafile = empty(&shadafile) ? empty(shadafile) ? - \ stdpath('state').'/shada/main.shada' : expand(shadafile) - \ : &shadafile ==# 'NONE' ? '' : &shadafile - if !empty(shadafile) && empty(glob(shadafile)) - " Since this may be the first time neovim has been run, we will try to - " create a shada file - try - wshada - catch /.*/ - let writeable = v:false - endtry - endif - if !writeable || (!empty(shadafile) && - \ (!filereadable(shadafile) || !filewritable(shadafile))) - let ok = v:false - call health#report_error('shada file is not '. - \ ((!writeable || filereadable(shadafile)) ? - \ 'writeable' : 'readable').":\n".shadafile) - endif - - if ok - call health#report_ok('no issues found') - endif -endfunction - -" Load the remote plugin manifest file and check for unregistered plugins -function! s:check_rplugin_manifest() abort - call health#report_start('Remote Plugins') - let existing_rplugins = {} - - for item in remote#host#PluginsForHost('python') - let existing_rplugins[item.path] = 'python' - endfor - - for item in remote#host#PluginsForHost('python3') - let existing_rplugins[item.path] = 'python3' - endfor - - let require_update = 0 - - for path in map(split(&runtimepath, ','), 'resolve(v:val)') - let python_glob = glob(path.'/rplugin/python*', 1, 1) - if empty(python_glob) - continue - endif - - let python_dir = python_glob[0] - let python_version = fnamemodify(python_dir, ':t') - - for script in glob(python_dir.'/*.py', 1, 1) - \ + glob(python_dir.'/*/__init__.py', 1, 1) - let contents = join(readfile(script)) - if contents =~# '\<\%(from\|import\)\s\+neovim\>' - if script =~# '[\/]__init__\.py$' - let script = tr(fnamemodify(script, ':h'), '\', '/') - endif - - if !has_key(existing_rplugins, script) - let msg = printf('"%s" is not registered.', fnamemodify(path, ':t')) - if python_version ==# 'pythonx' - if !has('python3') - let msg .= ' (python3 not available)' - endif - elseif !has(python_version) - let msg .= printf(' (%s not available)', python_version) - else - let require_update = 1 - endif - - call health#report_warn(msg) - endif - - break - endif - endfor - endfor - - if require_update - call health#report_warn('Out of date', ['Run `:UpdateRemotePlugins`']) - else - call health#report_ok('Up to date') - endif -endfunction - -function! s:check_performance() abort - call health#report_start('Performance') - - " check buildtype - let buildtype = matchstr(execute('version'), '\v\cbuild type:?\s*[^\n\r\t ]+') - if empty(buildtype) - call health#report_error('failed to get build type from :version') - elseif buildtype =~# '\v(MinSizeRel|Release|RelWithDebInfo)' - call health#report_ok(buildtype) - else - call health#report_info(buildtype) - call health#report_warn( - \ 'Non-optimized '.(has('debug')?'(DEBUG) ':'').'build. Nvim will be slower.', - \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', - \ s:suggest_faq]) - endif - - " check for slow shell invocation - let slow_cmd_time = 1.5 - let start_time = reltime() - call system('echo') - let elapsed_time = reltimefloat(reltime(start_time)) - if elapsed_time > slow_cmd_time - call health#report_warn( - \ 'Slow shell invocation (took '.printf('%.2f', elapsed_time).' seconds).') - endif -endfunction - -function! s:get_tmux_option(option) abort - let cmd = 'tmux show-option -qvg '.a:option " try global scope - let out = system(split(cmd)) - let val = substitute(out, '\v(\s|\r|\n)', '', 'g') - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - return 'error' - elseif empty(val) - let cmd = 'tmux show-option -qvgs '.a:option " try session scope - let out = system(split(cmd)) - let val = substitute(out, '\v(\s|\r|\n)', '', 'g') - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - return 'error' - endif - endif - return val -endfunction - -function! s:check_tmux() abort - if empty($TMUX) || !executable('tmux') - return - endif - call health#report_start('tmux') - - " check escape-time - let suggestions = ["set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10", - \ s:suggest_faq] - let tmux_esc_time = s:get_tmux_option('escape-time') - if tmux_esc_time !=# 'error' - if empty(tmux_esc_time) - call health#report_error('`escape-time` is not set', suggestions) - elseif tmux_esc_time > 300 - call health#report_error( - \ '`escape-time` ('.tmux_esc_time.') is higher than 300ms', suggestions) - else - call health#report_ok('escape-time: '.tmux_esc_time) - endif - endif - - " check focus-events - let suggestions = ["(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on"] - let tmux_focus_events = s:get_tmux_option('focus-events') - call health#report_info('Checking stuff') - if tmux_focus_events !=# 'error' - if empty(tmux_focus_events) || tmux_focus_events !=# 'on' - call health#report_warn( - \ "`focus-events` is not enabled. |'autoread'| may not work.", suggestions) - else - call health#report_ok('focus-events: '.tmux_focus_events) - endif - endif - - " check default-terminal and $TERM - call health#report_info('$TERM: '.$TERM) - let cmd = 'tmux show-option -qvg default-terminal' - let out = system(split(cmd)) - let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') - if empty(tmux_default_term) - let cmd = 'tmux show-option -qvgs default-terminal' - let out = system(split(cmd)) - let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') - endif - - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - elseif tmux_default_term !=# $TERM - call health#report_info('default-terminal: '.tmux_default_term) - call health#report_error( - \ '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.', - \ ['$TERM may have been set by some rc (.bashrc, .zshrc, ...).']) - elseif $TERM !~# '\v(tmux-256color|screen-256color)' - call health#report_error( - \ '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.', - \ ["Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal \"screen-256color\"", - \ s:suggest_faq]) - endif - - " check for RGB capabilities - let info = system(['tmux', 'server-info']) - let has_tc = stridx(info, " Tc: (flag) true") != -1 - let has_rgb = stridx(info, " RGB: (flag) true") != -1 - if !has_tc && !has_rgb - call health#report_warn( - \ "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.", - \ ["Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-overrides ',XXX:RGB'", - \ "For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'"]) - endif -endfunction - -function! s:check_terminal() abort - if !executable('infocmp') - return - endif - call health#report_start('terminal') - let cmd = 'infocmp -L' - let out = system(split(cmd)) - let kbs_entry = matchstr(out, 'key_backspace=[^,[:space:]]*') - 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: ' - \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) - call health#report_info('key_dc (kdch1) terminfo entry: ' - \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) - endif - for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] - if exists('$'.env_var) - call health#report_info(printf("$%s='%s'", env_var, eval('$'.env_var))) - endif - endfor -endfunction - -function! health#nvim#check() abort - call s:check_config() - call s:check_performance() - call s:check_rplugin_manifest() - call s:check_terminal() - call s:check_tmux() -endfunction diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 991bed6bbd..026c01bce6 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -139,7 +139,12 @@ function! provider#clipboard#Executable() abort let s:paste['*'] = s:paste['+'] return 'termux-clipboard' elseif !empty($TMUX) && executable('tmux') - let s:copy['+'] = ['tmux', 'load-buffer', '-'] + let [major, minor] = matchlist(systemlist(['tmux', '-V'])[0], 'tmux \(\d\+\)\.\(\d\+\)')[1:2] + if major > 3 || (major == 3 && minor >= 2) + let s:copy['+'] = ['tmux', 'load-buffer', '-w', '-'] + else + let s:copy['+'] = ['tmux', 'load-buffer', '-'] + endif let s:paste['+'] = ['tmux', 'save-buffer', '-'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim index 66f1cb46cb..4ae17815ba 100644 --- a/runtime/autoload/tohtml.vim +++ b/runtime/autoload/tohtml.vim @@ -712,6 +712,9 @@ func! tohtml#GetUserSettings() "{{{ call tohtml#GetOption(user_settings, 'no_foldcolumn', user_settings.ignore_folding) call tohtml#GetOption(user_settings, 'hover_unfold', 0 ) call tohtml#GetOption(user_settings, 'no_pre', 0 ) + call tohtml#GetOption(user_settings, 'no_doc', 0 ) + call tohtml#GetOption(user_settings, 'no_links', 0 ) + call tohtml#GetOption(user_settings, 'no_modeline', 0 ) call tohtml#GetOption(user_settings, 'no_invalid', 0 ) call tohtml#GetOption(user_settings, 'whole_filler', 0 ) call tohtml#GetOption(user_settings, 'use_xhtml', 0 ) @@ -752,7 +755,7 @@ func! tohtml#GetUserSettings() "{{{ " pre_wrap doesn't do anything if not using pre or not using CSS if user_settings.no_pre || !user_settings.use_css - let user_settings.pre_wrap=0 + let user_settings.pre_wrap = 0 endif "}}} diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index b949f3016e..8928650a39 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3267,6 +3267,12 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* pattern = { "*.py", "*.pyi" } < + Note: The `pattern` is passed to callbacks and commands as a literal string; environment + variables like `$HOME` and `~` are not automatically expanded as they are by |:autocmd|. Instead, + |expand()| such variables explicitly: > + pattern = vim.fn.expand("~") .. "/some/path/*.py" +< + Example values for event: > "BufWritePre" {"CursorHold", "BufWritePre", "BufWritePost"} @@ -3279,7 +3285,7 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* • group (string|integer) optional: the autocommand group name or id to match against. • pattern (string|array) optional: pattern or patterns to - match against |autocmd-pattern|. + match literally against |autocmd-pattern|. • buffer (integer) optional: buffer number for buffer local autocommands |autocmd-buflocal|. Cannot be used with {pattern}. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 2c00f755be..11352bb0c8 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -134,7 +134,8 @@ exists({expr}) Number |TRUE| if {expr} exists exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} -expandcmd({expr}) String expand {expr} like with `:edit` +expandcmd({string} [, {options}]) + String expand {string} like with `:edit` extend({expr1}, {expr2} [, {expr3}]) List/Dict insert items of {expr2} into {expr1} feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer @@ -220,6 +221,7 @@ gettabvar({nr}, {varname} [, {def}]) gettabwinvar({tabnr}, {winnr}, {name} [, {def}]) any {name} in {winnr} in tab page {tabnr} gettagstack([{nr}]) Dict get the tag stack of window {nr} +gettext({text}) String lookup translation of {text} getwininfo([{winid}]) List list of info about each window getwinpos([{timeout}]) List X and Y coord in pixels of the Vim window getwinposx() Number X coord in pixels of Vim window @@ -348,6 +350,7 @@ pyxeval({expr}) any evaluate |python_x| expression rand([{expr}]) Number get pseudo-random number range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} +readblob({fname}) Blob read a |Blob| from {fname} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} readfile({fname} [, {type} [, {max}]]) List get list of lines from file {fname} @@ -464,10 +467,11 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to ASCII/UTF-8 value str2nr({expr} [, {base} [, {quoted}]]) Number convert String to Number +strcharlen({expr}) Number character length of the String {expr} strcharpart({str}, {start} [, {len}]) String {len} characters of {str} at character {start} -strchars({expr} [, {skipcc}]) Number character length of the String {expr} +strchars({expr} [, {skipcc}]) Number character count of the String {expr} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} strftime({format} [, {time}]) String format time with a specified format strgetchar({str}, {index}) Number get char {index} from {str} @@ -652,9 +656,10 @@ appendbufline({buf}, {lnum}, {text}) *appendbufline()* For the use of {buf}, see |bufname()|. - {lnum} is used like with |append()|. Note that using |line()| - would use the current buffer, not the one appending to. - Use "$" to append at the end of the buffer. + {lnum} is the line number to append below. Note that using + |line()| would use the current buffer, not the one appending + to. Use "$" to append at the end of the buffer. Other string + values are not supported. On success 0 is returned, on failure 1 is returned. @@ -1197,11 +1202,9 @@ col({expr}) The result is a Number, which is the byte index of the column buffer. For the cursor position, when 'virtualedit' is active, the column is one higher if the cursor is after the end of the - line. This can be used to obtain the column in Insert mode: > - :imap <F2> <C-O>:let save_ve = &ve<CR> - \<C-O>:set ve=all<CR> - \<C-O>:echo col(".") .. "\n" <Bar> - \let &ve = save_ve<CR> + line. Also, when using a <Cmd> mapping the cursor isn't + moved, this can be used to obtain the column in Insert mode: > + :imap <F2> <Cmd>echo col(".")<CR> < Can also be used as a |method|: > GetPos()->col() @@ -2042,18 +2045,27 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* Can also be used as a |method|: > Getpattern()->expand() -expandcmd({string}) *expandcmd()* +expandcmd({string} [, {options}]) *expandcmd()* Expand special items in String {string} like what is done for an Ex command such as `:edit`. This expands special keywords, like with |expand()|, and environment variables, anywhere in {string}. "~user" and "~/path" are only expanded at the start. + + The following items are supported in the {options} Dict + argument: + errmsg If set to TRUE, error messages are displayed + if an error is encountered during expansion. + By default, error messages are not displayed. + Returns the expanded string. If an error is encountered during expansion, the unmodified {string} is returned. + Example: > :echo expandcmd('make %<.o') -< make /path/runtime/doc/builtin.o ~ - + make /path/runtime/doc/builtin.o + :echo expandcmd('make %<.o', {'errmsg': v:true}) +< Can also be used as a |method|: > GetCommand()->expandcmd() < @@ -3584,6 +3596,19 @@ gettagstack([{winnr}]) *gettagstack()* Can also be used as a |method|: > GetWinnr()->gettagstack() + +gettext({text}) *gettext()* + Translate String {text} if possible. + This is mainly for use in the distributed Vim scripts. When + generating message translations the {text} is extracted by + xgettext, the translator can add the translated message in the + .po file and Vim will lookup the translation when gettext() is + called. + For {text} double quoted strings are preferred, because + xgettext does not understand escaping in single quoted + strings. + + getwininfo([{winid}]) *getwininfo()* Returns information about windows as a |List| with Dictionaries. @@ -6024,6 +6049,14 @@ rand([{expr}]) *rand()* Can also be used as a |method|: > seed->rand() < + +readblob({fname}) *readblob()* + Read file {fname} in binary mode and return a |Blob|. + When the file can't be opened an error message is given and + the result is an empty |Blob|. + Also see |readfile()| and |writefile()|. + + *readdir()* readdir({directory} [, {expr}]) Return a list with file and directory names in {directory}. @@ -6058,6 +6091,7 @@ readdir({directory} [, {expr}]) Can also be used as a |method|: > GetDirName()->readdir() < + *readfile()* readfile({fname} [, {type} [, {max}]]) Read file {fname} and return a |List|, each line of the file @@ -6069,8 +6103,6 @@ readfile({fname} [, {type} [, {max}]]) - When the last line ends in a NL an extra empty list item is added. - No CR characters are removed. - When {type} contains "B" a |Blob| is returned with the binary - data of the file unmodified. Otherwise: - CR characters that appear before a NL are removed. - Whether the last line ends in a NL or not does not matter. @@ -6087,6 +6119,9 @@ readfile({fname} [, {type} [, {max}]]) Note that without {max} the whole file is read into memory. Also note that there is no recognition of encoding. Read a file into a buffer if you need to. + Deprecated (use |readblob()| instead): When {type} contains + "B" a |Blob| is returned with the binary data of the file + unmodified. When the file can't be opened an error message is given and the result is an empty list. Also see |writefile()|. @@ -7057,8 +7092,8 @@ setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()* GetLoclist()->setloclist(winnr) setmatches({list} [, {win}]) *setmatches()* - Restores a list of matches saved by |getmatches() for the - current window|. Returns 0 if successful, otherwise -1. All + Restores a list of matches saved by |getmatches()| for the + current window. Returns 0 if successful, otherwise -1. All current matches are cleared before the list is restored. See example for |getmatches()|. If {win} is specified, use the window with this number or @@ -7238,6 +7273,7 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()* *setreg()* setreg({regname}, {value} [, {options}]) Set the register {regname} to {value}. + If {regname} is "" or "@", the unnamed register '"' is used. The {regname} argument is a string. {value} may be any value returned by |getreg()| or @@ -7811,6 +7847,21 @@ str2nr({string} [, {base}]) *str2nr()* Can also be used as a |method|: > GetText()->str2nr() + +strcharlen({string}) *strcharlen()* + The result is a Number, which is the number of characters + in String {string}. Composing characters are ignored. + |strchars()| can count the number of characters, counting + composing characters separately. + + Returns 0 if {string} is empty or on error. + + Also see |strlen()|, |strdisplaywidth()| and |strwidth()|. + + Can also be used as a |method|: > + GetText()->strcharlen() + + strcharpart({src}, {start} [, {len}]) *strcharpart()* Like |strpart()| but using character index and length instead of byte index and length. Composing characters are counted @@ -7825,12 +7876,14 @@ strcharpart({src}, {start} [, {len}]) *strcharpart()* Can also be used as a |method|: > GetText()->strcharpart(5) + strchars({string} [, {skipcc}]) *strchars()* The result is a Number, which is the number of characters in String {string}. When {skipcc} is omitted or zero, composing characters are counted separately. When {skipcc} set to 1, Composing characters are ignored. + |strcharlen()| always does this. Returns zero on error. @@ -8895,7 +8948,12 @@ win_execute({id}, {command} [, {silent}]) *win_execute()* have unexpected side effects. Use |:noautocmd| if needed. Example: > call win_execute(winid, 'syntax enable') -< +< Doing the same with `setwinvar()` would not trigger + autocommands and not actually show syntax highlighting. + + When window {id} does not exist then no error is given and + an empty string is returned. + Can also be used as a |method|, the base is passed as the second argument: > GetCommand()->win_execute(winid) diff --git a/runtime/doc/dev_style.txt b/runtime/doc/dev_style.txt index 733b9c50c3..6afe2f7ee7 100644 --- a/runtime/doc/dev_style.txt +++ b/runtime/doc/dev_style.txt @@ -782,55 +782,6 @@ example, `"\uFEFF"`, is the Unicode zero-width no-break space character, which would be invisible if included in the source as straight UTF-8. -Function Declarations and Definitions ~ - -Return type on the same line as function name, parameters on the same line if -they fit. - -Functions look like this: > - - ReturnType function_name(Type par_name1, Type par_name2) - { - do_something(); - ... - } - -If you have too much text to fit on one line: > - - ReturnType really_long_function_name(Type par_name1, Type par_name2, - Type par_name3) - { - do_something(); - ... - } - -or if you cannot fit even the first parameter (but only then): > - - ReturnType really_really_really_long_function_name( - Type par_name1, // 4 space indent - Type par_name2, - Type par_name3) - { - do_something(); // 2 space indent - ... - } - -Some points to note: - -- The open parenthesis is always on the same line as the function name. -- There is never a space between the function name and the open parenthesis. -- There is never a space between the parentheses and the parameters. -- The open curly brace is always on the next line. -- The close curly brace is always on the last line by itself. -- There should be a space between the close parenthesis and the open curly - brace. -- All parameters should be named, with identical names in the declaration and - implementation. -- All parameters should be aligned if possible. -- Default indentation is 2 spaces. -- Wrapped parameters have a 4 space indent. - - Function Calls ~ On one line if it fits; otherwise, wrap arguments at the parenthesis. @@ -889,18 +840,6 @@ no name, assume a zero-length name. > interiorwrappinglist2 } }; -Conditionals ~ - -Don't use spaces inside parentheses. > - - if (condition) { // no spaces inside parentheses - ... // 2 space indent. - } else if (...) { // The else goes on the same line as the closing brace. - ... - } else { - ... - } - Loops and Switch Statements ~ Annotate non-trivial fall-through between cases. @@ -921,39 +860,6 @@ execute, simply `assert`: > assert(false); } -Pointer Expressions ~ - -No spaces around period or arrow. Pointer operators do not have trailing -spaces. - -The following are examples of correctly-formatted pointer and reference -expressions: > - - x = *p; - p = &x; - x = r.y; - x = r->y; - -Note that: - - - There are no spaces around the period or arrow when accessing a member. - - Pointer operators have no space after the * or &. - -Boolean Expressions ~ - -When you have a boolean expression that is longer than the standard line -length, keep operators at the start of the line. > - - if (this_one_thing > this_other_thing - && a_third_thing == a_fourth_thing - && yet_another && last_one) { - ... - } - -Also note that you should always use the punctuation operators, such as `&&` -and `~`, rather than the word operators, such as `and` and `compl`. - - Return Values ~ Do not needlessly surround the `return` expression with parentheses. @@ -1001,8 +907,6 @@ Use of horizontal whitespace depends on location. x = -5; // No spaces separating unary operators and their x++; // arguments. if (x && !y) - ... - i = (int)d; // No spaces after a cast operator. < Vertical Whitespace ~ diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 828093ddd4..d30d6c28a0 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -519,7 +519,7 @@ goto_prev({opts}) *vim.diagnostic.goto_prev()* Move to the previous diagnostic in the current buffer. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} (table|nil) See |vim.diagnostic.goto_next()| hide({namespace}, {bufnr}) *vim.diagnostic.hide()* Hide currently displayed diagnostics. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 21a30ca429..58a5cbc60c 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -545,6 +545,44 @@ Before editing binary, executable or Vim script files you should set the option. This will avoid the use of 'fileformat'. Without this you risk that single <NL> characters are unexpectedly replaced with <CR><NL>. +END OF LINE AND END OF FILE *eol-and-eof* + +Vim has several options to control the file format: + 'fileformat' the <EOL> style: Unix, DOS, Mac + 'endofline' whether the last line ends with a <EOL> + 'endoffile' whether the file ends with a CTRL-Z + 'fixendofline' whether to fix eol and eof + +The first three values are normally detected automatically when reading the +file and are used when writing the text to a file. While editing the buffer +it looks like every line has a line ending and the CTRL-Z isn't there (an +exception is when 'binary' is set, it works differently then). + +The 'fixendofline' option can be used to choose what to write. You can also +change the option values to write the file differently than how it was read. + +Here are some examples how to use them. + +If you want files in Unix format (every line NL terminated): > + setl ff=unix fixeol +You should probably do this on any Unix-like system. Also modern MS-Windows +systems tend to work well with this. It is recommended to always use this +format for Vim scripts. + +If you want to use an old MS-DOS file in a modern environment, fixing line +endings and dropping CTRL-Z, but keeping the <CR><NL> style <EOL>: > + setl ff=dos fixeol +This is useful for many MS-Windows programs, they regularly expect the +<CR><NL> line endings. + +If you want to drop the final <EOL> and add a final CTRL-Z (e.g. for an old +system like CP/M): > + setl ff=dos nofixeol noeol eof + +If you want to preserve the fileformat exactly as-is, including any final +<EOL> and final CTRL-Z: > + setl nofixeol + ============================================================================== 3. The argument list *argument-list* *arglist* diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt index 3a0bf2293e..a5d5b558dc 100644 --- a/runtime/doc/ft_rust.txt +++ b/runtime/doc/ft_rust.txt @@ -1,70 +1,70 @@ -*ft_rust.txt* For Vim version 8.1. Last change: 2017 Nov 02 +*ft_rust.txt* Nvim This is documentation for the Rust filetype plugin. ============================================================================== -CONTENTS *rust* +CONTENTS *rust* -1. Introduction |rust-intro| -2. Settings |rust-settings| -3. Commands |rust-commands| -4. Mappings |rust-mappings| +1. Introduction |rust-intro| +2. Settings |rust-settings| +3. Commands |rust-commands| +4. Mappings |rust-mappings| ============================================================================== -INTRODUCTION *rust-intro* +INTRODUCTION *rust-intro* This plugin provides syntax and supporting functionality for the Rust filetype. ============================================================================== -SETTINGS *rust-settings* +SETTINGS *rust-settings* This plugin has a few variables you can define in your vimrc that change the behavior of the plugin. - *g:rustc_path* + *g:rustc_path* g:rustc_path~ Set this option to the path to rustc for use in the |:RustRun| and |:RustExpand| commands. If unset, "rustc" will be located in $PATH: > let g:rustc_path = $HOME .. "/bin/rustc" < - *g:rustc_makeprg_no_percent* + *g:rustc_makeprg_no_percent* g:rustc_makeprg_no_percent~ Set this option to 1 to have 'makeprg' default to "rustc" instead of "rustc %": > let g:rustc_makeprg_no_percent = 1 < - *g:rust_conceal* + *g:rust_conceal* g:rust_conceal~ Set this option to turn on the basic |conceal| support: > let g:rust_conceal = 1 < - *g:rust_conceal_mod_path* + *g:rust_conceal_mod_path* g:rust_conceal_mod_path~ Set this option to turn on |conceal| for the path connecting token "::": > let g:rust_conceal_mod_path = 1 < - *g:rust_conceal_pub* + *g:rust_conceal_pub* g:rust_conceal_pub~ Set this option to turn on |conceal| for the "pub" token: > let g:rust_conceal_pub = 1 < - *g:rust_recommended_style* + *g:rust_recommended_style* g:rust_recommended_style~ - Set this option to enable vim indentation and textwidth settings to - conform to style conventions of the rust standard library (i.e. use 4 - spaces for indents and sets 'textwidth' to 99). This option is enabled + Set this option to enable vim indentation and textwidth settings to + conform to style conventions of the rust standard library (i.e. use 4 + spaces for indents and sets 'textwidth' to 99). This option is enabled by default. To disable it: > let g:rust_recommended_style = 0 < - *g:rust_fold* + *g:rust_fold* g:rust_fold~ Set this option to turn on |folding|: > let g:rust_fold = 1 @@ -76,39 +76,39 @@ g:rust_fold~ 2 Braced blocks are folded. 'foldlevel' is left at the global value (all folds are closed by default). - *g:rust_bang_comment_leader* + *g:rust_bang_comment_leader* g:rust_bang_comment_leader~ Set this option to 1 to preserve the leader on multi-line doc comments using the /*! syntax: > let g:rust_bang_comment_leader = 1 < - *g:ftplugin_rust_source_path* + *g:ftplugin_rust_source_path* g:ftplugin_rust_source_path~ Set this option to a path that should be prepended to 'path' for Rust source files: > let g:ftplugin_rust_source_path = $HOME .. '/dev/rust' < - *g:rustfmt_command* + *g:rustfmt_command* g:rustfmt_command~ Set this option to the name of the "rustfmt" executable in your $PATH. If not specified it defaults to "rustfmt" : > let g:rustfmt_command = 'rustfmt' < - *g:rustfmt_autosave* + *g:rustfmt_autosave* g:rustfmt_autosave~ Set this option to 1 to run |:RustFmt| automatically when saving a buffer. If not specified it defaults to 0 : > let g:rustfmt_autosave = 0 < - *g:rustfmt_fail_silently* + *g:rustfmt_fail_silently* g:rustfmt_fail_silently~ Set this option to 1 to prevent "rustfmt" from populating the |location-list| with errors. If not specified it defaults to 0: > let g:rustfmt_fail_silently = 0 < - *g:rustfmt_options* + *g:rustfmt_options* g:rustfmt_options~ Set this option to a string of options to pass to "rustfmt". The write-mode is already set to "overwrite". If not specified it @@ -116,13 +116,13 @@ g:rustfmt_options~ let g:rustfmt_options = '' < - *g:rust_playpen_url* + *g:rust_playpen_url* g:rust_playpen_url~ Set this option to override the URL for the playpen to use: > let g:rust_playpen_url = 'https://play.rust-lang.org/' < - *g:rust_shortener_url* + *g:rust_shortener_url* g:rust_shortener_url~ Set this option to override the URL for the URL shortener: > let g:rust_shortener_url = 'https://is.gd/' @@ -130,9 +130,9 @@ g:rust_shortener_url~ ============================================================================== -COMMANDS *rust-commands* +COMMANDS *rust-commands* -:RustRun [args] *:RustRun* +:RustRun [args] *:RustRun* :RustRun! [rustc-args] [--] [args] Compiles and runs the current file. If it has unsaved changes, it will be saved first using |:update|. If the current file is @@ -150,7 +150,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustExpand [args] *:RustExpand* +:RustExpand [args] *:RustExpand* :RustExpand! [TYPE] [args] Expands the current file using --pretty and displays the results in a new split. If the current file has unsaved @@ -169,7 +169,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitIr [args] *:RustEmitIr* +:RustEmitIr [args] *:RustEmitIr* Compiles the current file to LLVM IR and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -180,7 +180,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitAsm [args] *:RustEmitAsm* +:RustEmitAsm [args] *:RustEmitAsm* Compiles the current file to assembly and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -191,7 +191,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustPlay *:RustPlay* +:RustPlay *:RustPlay* This command will only work if you have web-api.vim installed (available at https://github.com/mattn/webapi-vim). It sends the current selection, or if nothing is selected, the entirety of the @@ -204,7 +204,7 @@ COMMANDS *rust-commands* |g:rust_shortener_url| is the base URL for the shortener, by default "https://is.gd/" -:RustFmt *:RustFmt* +:RustFmt *:RustFmt* Runs |g:rustfmt_command| on the current buffer. If |g:rustfmt_options| is set then those will be passed to the executable. @@ -214,12 +214,12 @@ COMMANDS *rust-commands* |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1 then it will not populate the |location-list|. -:RustFmtRange *:RustFmtRange* +:RustFmtRange *:RustFmtRange* Runs |g:rustfmt_command| with selected range. See |:RustFmt| for any other information. ============================================================================== -MAPPINGS *rust-mappings* +MAPPINGS *rust-mappings* This plugin defines mappings for |[[| and |]]| to support hanging indents. diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 9eed5a8966..376487ad1d 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1208,8 +1208,8 @@ tag command action ~ |:cgetfile| :cg[etfile] read file with error messages |:changes| :changes print the change list |:chdir| :chd[ir] change directory -|:checkhealth| :checkh[ealth] run healthchecks -|:checkpath| :che[ckpath] list included files +|:checkhealth| :che[ckhealth] run healthchecks +|:checkpath| :checkp[ath] list included files |:checktime| :checkt[ime] check timestamp of loaded buffers |:chistory| :chi[story] list the error lists |:clast| :cla[st] go to the specified error, default last one diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index ce16c208cd..c4e139bca7 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -677,6 +677,9 @@ vim.diff({a}, {b}, {opts}) *vim.diff()* • "unified": (default) String in unified format. • "indices": Array of hunk locations. Note: This option is ignored if `on_hunk` is used. + • `linematch` (boolean): Run linematch on the resulting hunks + from xdiff. Requires `result_type = indices`, ignored + otherwise. • `algorithm` (string): Diff algorithm to use. Values: • "myers" the default algorithm @@ -1573,7 +1576,7 @@ gsplit({s}, {sep}, {plain}) *vim.gsplit()* Parameters: ~ • {s} (string) String to split • {sep} (string) Separator or pattern - • {plain} (boolean) If `true` use `sep` literally (passed to + • {plain} (boolean|nil) If `true` use `sep` literally (passed to string.find) Return: ~ @@ -1645,15 +1648,11 @@ split({s}, {sep}, {kwargs}) *vim.split()* split("|x|y|z|", "|", {trimempty=true}) => {'x', 'y', 'z'} < - @alias split_kwargs {plain: boolean, trimempty: boolean} | boolean | nil - - See also: ~ - |vim.gsplit()| - Parameters: ~ • {s} (string) String to split • {sep} (string) Separator or pattern - • {kwargs} (table|nil) Keyword arguments: + • {kwargs} ({plain: boolean, trimempty: boolean}|nil) Keyword + arguments: • plain: (boolean) If `true` use `sep` literally (passed to string.find) • trimempty: (boolean) If `true` remove empty items from the @@ -1662,6 +1661,9 @@ split({s}, {sep}, {kwargs}) *vim.split()* Return: ~ string[] List of split components + See also: ~ + |vim.gsplit()| + startswith({s}, {prefix}) *vim.startswith()* Tests if `s` starts with `prefix`. diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 74ceb08994..d06a970340 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -601,6 +601,7 @@ probably means that some other program changed the file. You will have to find out what happened, and decide which version of the file you want to keep. Set the 'autoread' option if you want to do this automatically. This message is not given when 'buftype' is not empty. +Also see the |FileChangedShell| autocommand. There is one situation where you get this message even though there is nothing wrong: If you save a file in Windows on the day the daylight saving time diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index a04d02946d..818c6d0115 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1053,19 +1053,26 @@ A jump table for the options with a short description can be found at |Q_op|. text should normally be narrower. This prevents text indented almost to the right window border occupying lot of vertical space when broken. + (default: 20) shift:{n} After applying 'breakindent', the wrapped line's beginning will be shifted by the given number of characters. It permits dynamic French paragraph indentation (negative) or emphasizing the line continuation (positive). + (default: 0) sbr Display the 'showbreak' value before applying the additional indent. + (default: off) list:{n} Adds an additional indent for lines that match a numbered or bulleted list (using the 'formatlistpat' setting). list:-1 Uses the length of a match with 'formatlistpat' for indentation. - The default value for min is 20, shift and list is 0. + (default: 0) + column:{n} Indent at column {n}. Will overrule the other + sub-options. Note: an additional indent may be + added for the 'showbreak' setting. + (default: off) *'browsedir'* *'bsdir'* 'browsedir' 'bsdir' string (default: "last") @@ -2017,6 +2024,16 @@ A jump table for the options with a short description can be found at |Q_op|. Use the indent heuristic for the internal diff library. + linematch:{n} Enable a second stage diff on each generated + hunk in order to align lines. When the total + number of lines in a hunk exceeds {n}, the + second stage diff will not be performed as + very large hunks can cause noticeable lag. A + recommended setting is "linematch:60", as this + will enable alignment for a 2 buffer diff with + hunks of up to 30 lines each, or a 3 buffer + diff with hunks of up to 20 lines each. + algorithm:{text} Use the specified diff algorithm with the internal diff engine. Currently supported algorithms are: @@ -2135,6 +2152,16 @@ A jump table for the options with a short description can be found at |Q_op|. See 'fileencoding' to control file-content encoding. + *'endoffile'* *'eof'* *'noendoffile'* *'noeof'* +'endoffile' 'eof' boolean (default on) + local to buffer + Indicates that a CTRL-Z character was found at the end of the file + when reading it. Normally only happens when 'fileformat' is "dos". + When writing a file and this option is off and the 'binary' option + is on, or 'fixeol' option is off, no CTRL-Z will be written at the + end of the file. + See |eol-and-eof| for example settings. + *'endofline'* *'eol'* *'noendofline'* *'noeol'* 'endofline' 'eol' boolean (default on) local to buffer @@ -2149,6 +2176,7 @@ A jump table for the options with a short description can be found at |Q_op|. to remember the presence of a <EOL> for the last line in the file, so that when you write the file the situation from the original file can be kept. But you can change it if you want to. + See |eol-and-eof| for example settings. *'equalalways'* *'ea'* *'noequalalways'* *'noea'* 'equalalways' 'ea' boolean (default on) @@ -2490,11 +2518,12 @@ A jump table for the options with a short description can be found at |Q_op|. 'fixendofline' 'fixeol' boolean (default on) local to buffer When writing a file and this option is on, <EOL> at the end of file - will be restored if missing. Turn this option off if you want to + will be restored if missing. Turn this option off if you want to preserve the situation from the original file. When the 'binary' option is set the value of this option doesn't matter. See the 'endofline' option. + See |eol-and-eof| for example settings. *'foldclose'* *'fcl'* 'foldclose' 'fcl' string (default "") diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index 371a210847..5357aaa3f1 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -372,7 +372,7 @@ Vim includes two regexp engines: 1. An old, backtracking engine that supports everything. 2. A new, NFA engine that works much faster on some patterns, possibly slower on some patterns. - + *E1281* Vim will automatically select the right engine for you. However, if you run into a problem or want to specifically select one engine or the other, you can prepend one of the following to the pattern: diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt index 8a6437fdc8..5ba5d1beef 100644 --- a/runtime/doc/pi_health.txt +++ b/runtime/doc/pi_health.txt @@ -21,8 +21,8 @@ Plugin authors are encouraged to write new healthchecks. |health-dev| ============================================================================== Commands *health-commands* - *:checkhealth* *:CheckHealth* -:checkhealth Run all healthchecks. + *:che* *:checkhealth* *:CheckHealth* +:che[ckhealth] Run all healthchecks. *E5009* Nvim depends on |$VIMRUNTIME|, 'runtimepath' and 'packpath' to find the standard "runtime files" for syntax highlighting, @@ -30,7 +30,7 @@ Commands *health-commands* :checkhealth). If the runtime files cannot be found then those features will not work. -:checkhealth {plugins} +:che[ckhealth] {plugins} Run healthcheck(s) for one or more plugins. E.g. to run only the standard Nvim healthcheck: > :checkhealth nvim diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index c6c0596ea0..0b2ab1690b 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -80,20 +80,20 @@ Copyright 2005-2017: *tar-copyright* These options are variables that one may change, typically in one's <.vimrc> file. - Default - Variable Value Explanation + Default + Variable Value Explanation *g:tar_browseoptions* "Ptf" used to get a list of contents - *g:tar_readoptions* "OPxf" used to extract a file from a tarball - *g:tar_cmd* "tar" the name of the tar program - *g:tar_nomax* 0 if true, file window will not be maximized - *g:tar_secure* undef if exists: + *g:tar_readoptions* "OPxf" used to extract a file from a tarball + *g:tar_cmd* "tar" the name of the tar program + *g:tar_nomax* 0 if true, file window will not be maximized + *g:tar_secure* undef if exists: "--"s will be used to prevent unwanted option expansion in tar commands. Please be sure that your tar command accepts "--"; Posix compliant tar utilities do accept them. if not exists: - The tar plugin will reject any tar + The tar plugin will reject any tar files or member files that begin with "-" Not all tar's support the "--" which is why diff --git a/runtime/doc/pi_zip.txt b/runtime/doc/pi_zip.txt index 2bbd6eea06..9b531d78b4 100644 --- a/runtime/doc/pi_zip.txt +++ b/runtime/doc/pi_zip.txt @@ -39,7 +39,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* OPTIONS~ - *g:zip_nomax* + *g:zip_nomax* If this variable exists and is true, the file window will not be automatically maximized when opened. @@ -54,21 +54,21 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* under Windows ("). If you'd rather have no quotes, simply set g:zip_shq to the empty string (let g:zip_shq= "") in your <.vimrc>. - *g:zip_unzipcmd* + *g:zip_unzipcmd* Use this option to specify the program which does the duty of "unzip". It's used during browsing. By default: > - let g:zip_unzipcmd= "unzip" + let g:zip_unzipcmd= "unzip" < *g:zip_zipcmd* Use this option to specify the program which does the duty of "zip". It's used during the writing (updating) of a file already in a zip file; by default: > - let g:zip_zipcmd= "zip" + let g:zip_zipcmd= "zip" < *g:zip_extractcmd* This option specifies the program (and any options needed) used to extract a file from a zip archive. By default, > - let g:zip_extractcmd= g:zip_unzipcmd + let g:zip_extractcmd= g:zip_unzipcmd < PREVENTING LOADING~ @@ -103,14 +103,14 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* ============================================================================== 4. History *zip-history* {{{1 v32 Oct 22, 2021 * to avoid an issue with a vim 8.2 patch, zipfile: has - been changed to zipfile:// . This often shows up + been changed to zipfile:// . This often shows up as zipfile:/// with zipped files that are root-based. v29 Apr 02, 2017 * (Klartext) reported that an encrypted zip file could - opened but the swapfile held unencrypted contents. + opened but the swapfile held unencrypted contents. The solution is to edit the contents of a zip file using the |:noswapfile| modifier. v28 Oct 08, 2014 * changed the sanity checks for executables to reflect - the command actually to be attempted in zip#Read() + the command actually to be attempted in zip#Read() and zip#Write() * added the extraction of a file capability Nov 30, 2015 * added *.epub to the |g:zipPlugin_ext| list diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index f1294a18ba..e4a3dae08b 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -689,6 +689,7 @@ Short explanation of each option: *option-list* 'display' 'dy' list of flags for how to display text 'eadirection' 'ead' in which direction 'equalalways' works 'encoding' 'enc' encoding used internally +'endoffile' 'eof' write CTRL-Z at end of the file 'endofline' 'eol' write <EOL> for last line in file 'equalalways' 'ea' windows are automatically made the same size 'equalprg' 'ep' external program to use for "=" command @@ -827,7 +828,7 @@ Short explanation of each option: *option-list* 'printmbcharset' 'pmbcs' CJK character set to be used for :hardcopy 'printmbfont' 'pmbfn' font names to be used for CJK output of :hardcopy 'printoptions' 'popt' controls the format of :hardcopy output -'pumheight' 'ph' maximum height of the popup menu +'pumheight' 'ph' maximum number of items to show in the popup menu 'pumwidth' 'pw' minimum width of the popup menu 'pyxversion' 'pyx' Python version used for pyx* commands 'quoteescape' 'qe' escape characters used in a string diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 6a1b8b05a7..1bbd20702b 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -689,7 +689,7 @@ found automatically. Your package would have these files: < pack/foo/start/lib/autoload/foolib.vim > func foolib#getit() -This works, because start packages will be searchd for autoload files, when +This works, because start packages will be searched for autoload files, when sourcing the plugins. ============================================================================== diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 48301f3083..693e78cf9c 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -464,7 +464,7 @@ Force to omit the line numbers: > Go back to the default to use 'number' by deleting the variable: > :unlet g:html_number_lines < - *g:html_line_ids* + *g:html_line_ids* Default: 1 if |g:html_number_lines| is set, 0 otherwise. When 1, adds an HTML id attribute to each line number, or to an empty <span> inserted for that purpose if no line numbers are shown. This ID attribute @@ -656,6 +656,22 @@ the rendered page generated by 2html.vim. > :let g:html_no_pre = 1 < + *g:html_no_doc* +Default: 0. +When 1 it doesn't generate a full HTML document with a DOCTYPE, <head>, +<body>, etc. If |g:html_use_css| is enabled (the default) you'll have to +define the CSS manually. The |g:html_dynamic_folds| and |g:html_line_ids| +settings (off by default) also insert some JavaScript. + + + *g:html_no_links* +Default: 0. +Don't generate <a> tags for text that looks like an URL. + + *g:html_no_modeline* +Default: 0. +Don't generate a modeline disabling folding. + *g:html_expand_tabs* Default: 0 if 'tabstop' is 8, 'expandtab' is 0, 'vartabstop' is not in use, and no fold column or line numbers occur in the generated HTML; @@ -2897,7 +2913,7 @@ Default folding is rather detailed, i.e., small syntax units like "if", "do", You can set "ruby_foldable_groups" to restrict which groups are foldable: > - :let ruby_foldable_groups = 'if case %' + :let ruby_foldable_groups = 'if case %' < The value is a space-separated list of keywords: @@ -2905,22 +2921,22 @@ The value is a space-separated list of keywords: -------- ------------------------------------- ~ ALL Most block syntax (default) NONE Nothing - if "if" or "unless" block + if "if" or "unless" block def "def" block class "class" block module "module" block - do "do" block + do "do" block begin "begin" block case "case" block for "for", "while", "until" loops - { Curly bracket block or hash literal - [ Array literal - % Literal with "%" notation, e.g.: %w(STRING), %!STRING! - / Regexp + { Curly bracket block or hash literal + [ Array literal + % Literal with "%" notation, e.g.: %w(STRING), %!STRING! + / Regexp string String and shell command output (surrounded by ', ", `) - : Symbol - # Multiline comment - << Here documents + : Symbol + # Multiline comment + << Here documents __END__ Source code after "__END__" directive *ruby_no_expensive* @@ -3750,7 +3766,7 @@ SYNTAX ISKEYWORD SETTING *:syn-iskeyword* clear: Syntax specific iskeyword setting is disabled and the buffer-local 'iskeyword' setting is used. - {option} Set the syntax 'iskeyword' option to a new value. + {option} Set the syntax 'iskeyword' option to a new value. Example: > :syntax iskeyword @,48-57,192-255,$,_ @@ -5110,7 +5126,7 @@ guisp={color-name} *guisp* "gg" is the Green value "bb" is the Blue value All values are hexadecimal, range from "00" to "ff". Examples: > - :highlight Comment guifg=#11f0c3 guibg=#ff00ff + :highlight Comment guifg=#11f0c3 guibg=#ff00ff < blend={integer} *highlight-blend* Override the blend level for a highlight group within the popupmenu diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 143930c48a..d079db0717 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -829,10 +829,10 @@ CTRL-W d Open a new window, with the cursor on the first (default: whole file). See |:search-args| for [/] and [!]. - *:che* *:chec* *:check* *:checkpath* -:che[ckpath] List all the included files that could not be found. + *:checkp* *:checkpath* +:checkp[ath] List all the included files that could not be found. -:che[ckpath]! List all the included files. +:checkp[ath]! List all the included files. *:search-args* Common arguments for the commands above: diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index 46e0371daa..2a131a34dd 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -380,6 +380,8 @@ and then drag) will result in whole words to be selected. This continues until the button is released, at which point the selection is per character again. +For scrolling with the mouse see |scroll-mouse-wheel|. + In Insert mode, when a selection is started, Vim goes into Normal mode temporarily. When Visual or Select mode ends, it returns to Insert mode. This is like using CTRL-O in Insert mode. Select mode is used when the diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt index f4375c3363..56e0dad656 100644 --- a/runtime/doc/testing.txt +++ b/runtime/doc/testing.txt @@ -98,18 +98,46 @@ assert_exception({error} [, {msg}]) *assert_exception()* catch call assert_exception('E492:') endtry - -assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()* +< + *assert_fails()* +assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]]) Run {cmd} and add an error message to |v:errors| if it does - NOT produce an error. Also see |assert-return|. - When {error} is given it must match in |v:errmsg|. + NOT produce an error or when {error} is not found in the + error message. Also see |assert-return|. + + When {error} is a string it must be found literally in the + first reported error. Most often this will be the error code, + including the colon, e.g. "E123:". > + assert_fails('bad cmd', 'E987:') +< + When {error} is a |List| with one or two strings, these are + used as patterns. The first pattern is matched against the + first reported error: > + assert_fails('cmd', ['E987:.*expected bool']) +< The second pattern, if present, is matched against the last + reported error. To only match the last error use an empty + string for the first error: > + assert_fails('cmd', ['', 'E987:']) +< + If {msg} is empty then it is not used. Do this to get the + default message when passing the {lnum} argument. + + When {lnum} is present and not negative, and the {error} + argument is present and matches, then this is compared with + the line number at which the error was reported. That can be + the line number in a function or in a script. + + When {context} is present it is used as a pattern and matched + against the context (script name or function name) where + {lnum} is located in. + Note that beeping is not considered an error, and some failing commands only beep. Use |assert_beeps()| for those. Can also be used as a |method|: > GetCmd()->assert_fails('E99:') -assert_false({actual} [, {msg}]) *assert_false()* +assert_false({actual} [, {msg}]) *assert_false()* When {actual} is not false an error message is added to |v:errors|, like with |assert_equal()|. Also see |assert-return|. diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 63714595eb..944eb1fc80 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -127,8 +127,8 @@ Display matches for a search pattern while you type. This defines a key mapping. More about that in the next section. This defines the "Q" command to do formatting with the "gq" operator. This is how -it worked before Vim 5.0. Otherwise the "Q" command starts Ex mode, but you -will not need it. +it worked before Vim 5.0. Otherwise the "Q" command repeats the last recorded +register. > vnoremap _g y:exe "grep /" .. escape(@", '\\/') .. "/ *.c *.h"<CR> diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index d2db967c97..067ad6648c 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -619,7 +619,8 @@ String manipulation: *string-functions* stridx() first index of a short string in a long string strridx() last index of a short string in a long string strlen() length of a string in bytes - strchars() length of a string in characters + strcharlen() length of a string in characters + strchars() number of characters in a string strwidth() size of string when displayed strdisplaywidth() size of string when displayed, deals with tabs setcellwidths() set character cell width overrides @@ -639,6 +640,7 @@ String manipulation: *string-functions* execute() execute an Ex command and get the output win_execute() like execute() but in a specified window trim() trim characters from a string + gettext() lookup message translation List manipulation: *list-functions* get() get an item without error for wrong index @@ -817,6 +819,7 @@ System functions and manipulation of files: setenv() set an environment variable hostname() name of the system readfile() read a file into a List of lines + readblob() read a file into a Blob readdir() get a List of file names in a directory writefile() write a List of lines or Blob into a file diff --git a/runtime/ftplugin/apache.vim b/runtime/ftplugin/apache.vim new file mode 100644 index 0000000000..9f612f5447 --- /dev/null +++ b/runtime/ftplugin/apache.vim @@ -0,0 +1,16 @@ +" Vim filetype plugin +" Language: apache configuration file +" Maintainer: Per Juchtmans <dubgeiser+vimNOSPAM@gmail.com> +" Last Change: 2022 Oct 22 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=:# +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setlocal comments< commentstring<" + +" vim: nowrap sw=2 sts=2 ts=8 noet: diff --git a/runtime/ftplugin/openvpn.vim b/runtime/ftplugin/openvpn.vim new file mode 100644 index 0000000000..56c0f25b39 --- /dev/null +++ b/runtime/ftplugin/openvpn.vim @@ -0,0 +1,14 @@ +" Vim filetype plugin +" Language: OpenVPN +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Last Change: 2022 Oct 16 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setlocal iskeyword+=-,.,/ +setlocal comments=:#,:; commentstring=#%s + +let b:undo_ftplugin = 'setl isk< com< cms<' diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 6eebee6376..5951884e07 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -457,7 +457,7 @@ local function get_page(path, silent) end local function put_page(page) - vim.bo.modified = true + vim.bo.modifiable = true vim.bo.readonly = false vim.bo.swapfile = false diff --git a/runtime/lua/man/health.lua b/runtime/lua/man/health.lua deleted file mode 100644 index 11d7148216..0000000000 --- a/runtime/lua/man/health.lua +++ /dev/null @@ -1,20 +0,0 @@ -local M = {} - -local report_ok = vim.fn['health#report_ok'] -local report_error = vim.fn['health#report_error'] - -local function check_runtime_file(name) - local path = vim.env.VIMRUNTIME .. '/' .. name - if vim.loop.fs_stat(path) then - report_error(string.format('%s detected. Please delete %s', name, path)) - else - report_ok(string.format('%s not in $VIMRUNTIME', name)) - end -end - -function M.check() - check_runtime_file('plugin/man.vim') - check_runtime_file('autoload/man.vim') -end - -return M diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua new file mode 100644 index 0000000000..413eae0bf9 --- /dev/null +++ b/runtime/lua/nvim/health.lua @@ -0,0 +1,389 @@ +local M = {} +local health = require('vim.health') + +local suggest_faq = 'https://github.com/neovim/neovim/wiki/FAQ' + +local function check_runtime() + health.report_start('Runtime') + -- Files from an old installation. + local bad_files = { + ['plugin/man.vim'] = false, + ['scripts.vim'] = false, + ['autoload/man.vim'] = false, + } + local bad_files_msg = '' + for k, _ in pairs(bad_files) do + local path = ('%s/%s'):format(vim.env.VIMRUNTIME, k) + if vim.loop.fs_stat(path) then + bad_files[k] = true + bad_files_msg = ('%s%s\n'):format(bad_files_msg, path) + end + end + + local ok = (bad_files_msg == '') + local info = ok and health.report_ok or health.report_info + info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME)) + if not ok then + health.report_error( + string.format( + '$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s', + bad_files_msg + ), + { 'Delete $VIMRUNTIME (or uninstall Nvim), then reinstall Nvim.' } + ) + end +end + +local function check_config() + health.report_start('Configuration') + local ok = true + local empty = function(o) + return 0 ~= vim.fn.empty(o) + end + local filereadable = function(o) + return 0 ~= vim.fn.filereadable(o) + end + local filewritable = function(o) + return 0 ~= vim.fn.filewritable(o) + end + + local vimrc = ( + empty(vim.env.MYVIMRC) and vim.fn.stdpath('config') .. '/init.vim' or vim.env.MYVIMRC + ) + if not filereadable(vimrc) then + ok = false + local has_vim = filereadable(vim.fn.expand('~/.vimrc')) + health.report_warn( + (-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc, + { has_vim and ':help nvim-from-vim' or ':help init.vim' } + ) + end + + -- If $VIM is empty we don't care. Else make sure it is valid. + if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then + ok = false + health.report_error('$VIM is invalid: ' .. vim.env.VIM) + end + + if 1 == vim.fn.exists('$NVIM_TUI_ENABLE_CURSOR_SHAPE') then + ok = false + health.report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', { + "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", + 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402', + }) + end + + if vim.v.ctype == 'C' then + ok = false + health.report_error( + 'Locale does not support UTF-8. Unicode characters may not display correctly.' + .. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format( + vim.env.LANG, + vim.env.LC_ALL, + vim.env.LC_CTYPE + ), + { + 'If using tmux, try the -u option.', + 'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.', + 'Configure your system locale.', + } + ) + end + + if vim.o.paste == 1 then + ok = false + health.report_error( + "'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", + { + 'Remove `set paste` from your init.vim, if applicable.', + 'Check `:verbose set paste?` to see if a plugin or script set the option.', + } + ) + end + + local writeable = true + local shadaopt = vim.fn.split(vim.o.shada, ',') + local shadafile = ( + empty(vim.o.shada) and vim.o.shada + or vim.fn.substitute(vim.fn.matchstr(shadaopt[#shadaopt], '^n.\\+'), '^n', '', '') + ) + shadafile = ( + empty(vim.o.shadafile) + and (empty(shadafile) and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fn.expand( + shadafile + )) + or (vim.o.shadafile == 'NONE' and '' or vim.o.shadafile) + ) + if not empty(shadafile) and empty(vim.fn.glob(shadafile)) then + -- Since this may be the first time Nvim has been run, try to create a shada file. + if not pcall(vim.cmd.wshada) then + writeable = false + end + end + if + not writeable + or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile))) + then + ok = false + health.report_error( + 'shada file is not ' + .. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable') + .. ':\n' + .. shadafile + ) + end + + if ok then + health.report_ok('no issues found') + end +end + +local function check_performance() + vim.api.nvim_exec([=[ + func! s:check_performance() abort + let s:suggest_faq = ']=] .. suggest_faq .. [=[' + + call health#report_start('Performance') + + " check buildtype + let s:buildtype = matchstr(execute('version'), '\v\cbuild type:?\s*[^\n\r\t ]+') + if empty(s:buildtype) + call health#report_error('failed to get build type from :version') + elseif s:buildtype =~# '\v(MinSizeRel|Release|RelWithDebInfo)' + call health#report_ok(s:buildtype) + else + call health#report_info(s:buildtype) + call health#report_warn( + \ 'Non-optimized '.(has('debug')?'(DEBUG) ':'').'build. Nvim will be slower.', + \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', + \ s:suggest_faq]) + endif + + " check for slow shell invocation + let s:slow_cmd_time = 1.5 + let s:start_time = reltime() + call system('echo') + let s:elapsed_time = reltimefloat(reltime(s:start_time)) + if s:elapsed_time > s:slow_cmd_time + call health#report_warn( + \ 'Slow shell invocation (took '.printf('%.2f', s:elapsed_time).' seconds).') + endif + endf + + call s:check_performance() + ]=], false) +end + +-- Load the remote plugin manifest file and check for unregistered plugins +local function check_rplugin_manifest() + vim.api.nvim_exec( + [=[ + func! s:check_rplugin_manifest() abort + call health#report_start('Remote Plugins') + let existing_rplugins = {} + + for item in remote#host#PluginsForHost('python') + let existing_rplugins[item.path] = 'python' + endfor + + for item in remote#host#PluginsForHost('python3') + let existing_rplugins[item.path] = 'python3' + endfor + + let require_update = 0 + + for path in map(split(&runtimepath, ','), 'resolve(v:val)') + let python_glob = glob(path.'/rplugin/python*', 1, 1) + if empty(python_glob) + continue + endif + + let python_dir = python_glob[0] + let python_version = fnamemodify(python_dir, ':t') + + for script in glob(python_dir.'/*.py', 1, 1) + \ + glob(python_dir.'/*/__init__.py', 1, 1) + let contents = join(readfile(script)) + if contents =~# '\<\%(from\|import\)\s\+neovim\>' + if script =~# '[\/]__init__\.py$' + let script = tr(fnamemodify(script, ':h'), '\', '/') + endif + + if !has_key(existing_rplugins, script) + let msg = printf('"%s" is not registered.', fnamemodify(path, ':t')) + if python_version ==# 'pythonx' + if !has('python3') + let msg .= ' (python3 not available)' + endif + elseif !has(python_version) + let msg .= printf(' (%s not available)', python_version) + else + let require_update = 1 + endif + + call health#report_warn(msg) + endif + + break + endif + endfor + endfor + + if require_update + call health#report_warn('Out of date', ['Run `:UpdateRemotePlugins`']) + else + call health#report_ok('Up to date') + endif + endf + + call s:check_rplugin_manifest() + ]=], + false + ) +end + +local function check_tmux() + vim.api.nvim_exec([=[ + let s:suggest_faq = ']=] .. suggest_faq .. [=[' + + func! s:get_tmux_option(option) abort + let cmd = 'tmux show-option -qvg '.a:option " try global scope + let out = system(split(cmd)) + let val = substitute(out, '\v(\s|\r|\n)', '', 'g') + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + return 'error' + elseif empty(val) + let cmd = 'tmux show-option -qvgs '.a:option " try session scope + let out = system(split(cmd)) + let val = substitute(out, '\v(\s|\r|\n)', '', 'g') + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + return 'error' + endif + endif + return val + endf + + func! s:check_tmux() abort + if empty($TMUX) || !executable('tmux') + return + endif + call health#report_start('tmux') + + " check escape-time + let suggestions = ["set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10", + \ s:suggest_faq] + let tmux_esc_time = s:get_tmux_option('escape-time') + if tmux_esc_time !=# 'error' + if empty(tmux_esc_time) + call health#report_error('`escape-time` is not set', suggestions) + elseif tmux_esc_time > 300 + call health#report_error( + \ '`escape-time` ('.tmux_esc_time.') is higher than 300ms', suggestions) + else + call health#report_ok('escape-time: '.tmux_esc_time) + endif + endif + + " check focus-events + let suggestions = ["(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on"] + let tmux_focus_events = s:get_tmux_option('focus-events') + if tmux_focus_events !=# 'error' + if empty(tmux_focus_events) || tmux_focus_events !=# 'on' + call health#report_warn( + \ "`focus-events` is not enabled. |'autoread'| may not work.", suggestions) + else + call health#report_ok('focus-events: '.tmux_focus_events) + endif + endif + + " check default-terminal and $TERM + call health#report_info('$TERM: '.$TERM) + let cmd = 'tmux show-option -qvg default-terminal' + let out = system(split(cmd)) + let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') + if empty(tmux_default_term) + let cmd = 'tmux show-option -qvgs default-terminal' + let out = system(split(cmd)) + let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') + endif + + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + elseif tmux_default_term !=# $TERM + call health#report_info('default-terminal: '.tmux_default_term) + call health#report_error( + \ '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.', + \ ['$TERM may have been set by some rc (.bashrc, .zshrc, ...).']) + elseif $TERM !~# '\v(tmux-256color|screen-256color)' + call health#report_error( + \ '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.', + \ ["Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal \"screen-256color\"", + \ s:suggest_faq]) + endif + + " check for RGB capabilities + let info = system(['tmux', 'show-messages', '-JT']) + let has_tc = stridx(info, " Tc: (flag) true") != -1 + let has_rgb = stridx(info, " RGB: (flag) true") != -1 + if !has_tc && !has_rgb + call health#report_warn( + \ "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.", + \ ["Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-overrides ',XXX:RGB'", + \ "For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'"]) + endif + endf + + call s:check_tmux() + ]=], false) +end + +local function check_terminal() + vim.api.nvim_exec( + [=[ + func! s:check_terminal() abort + if !executable('infocmp') + return + endif + call health#report_start('terminal') + let cmd = 'infocmp -L' + let out = system(split(cmd)) + let kbs_entry = matchstr(out, 'key_backspace=[^,[:space:]]*') + 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: ' + \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) + call health#report_info('key_dc (kdch1) terminfo entry: ' + \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) + endif + for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] + if exists('$'.env_var) + call health#report_info(printf("$%s='%s'", env_var, eval('$'.env_var))) + endif + endfor + endf + + call s:check_terminal() + ]=], + false + ) +end + +function M.check() + check_config() + check_runtime() + check_performance() + check_rplugin_manifest() + check_terminal() + check_tmux() +end + +return M diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 84091fbf0e..af2d41bf7e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -797,7 +797,7 @@ function M.get_prev_pos(opts) end --- Move to the previous diagnostic in the current buffer. ----@param opts table See |vim.diagnostic.goto_next()| +---@param opts table|nil See |vim.diagnostic.goto_next()| function M.goto_prev(opts) return diagnostic_move_pos(opts, M.get_prev_pos(opts)) end diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a574ab1942..56659eff04 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -170,7 +170,7 @@ local extension = { return require('vim.filetype.detect').bindzone(bufnr, 'dcl') end, db = function(path, bufnr) - return require('vim.filetype.detect').bindzone(bufnr, '') + return require('vim.filetype.detect').bindzone(bufnr) end, bicep = 'bicep', bb = 'bitbake', @@ -1791,6 +1791,8 @@ local pattern = { ['.*/etc/DIR_COLORS'] = 'dircolors', ['.*/etc/dnsmasq%.conf'] = 'dnsmasq', ['php%.ini%-.*'] = 'dosini', + ['.*/%.aws/config'] = 'confini', + ['.*/%.aws/credentials'] = 'confini', ['.*/etc/pacman%.conf'] = 'confini', ['.*/etc/yum%.conf'] = 'dosini', ['.*lvs'] = 'dracula', diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 93fd621161..c648a53555 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -519,15 +519,15 @@ M['window/showDocument'] = function(_, result, ctx, _) -- TODO(lvimuser): ask the user for confirmation local cmd if vim.fn.has('win32') == 1 then - cmd = { 'cmd.exe', '/c', 'start', '""', vim.fn.shellescape(uri) } + cmd = { 'cmd.exe', '/c', 'start', '""', uri } elseif vim.fn.has('macunix') == 1 then - cmd = { 'open', vim.fn.shellescape(uri) } + cmd = { 'open', uri } else - cmd = { 'xdg-open', vim.fn.shellescape(uri) } + cmd = { 'xdg-open', uri } end local ret = vim.fn.system(cmd) - if vim.v.shellerror ~= 0 then + if vim.v.shell_error ~= 0 then return { success = false, error = { diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index f03d608e56..f980547ae4 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -62,7 +62,7 @@ end)() --- ---@param s string String to split ---@param sep string Separator or pattern ----@param plain boolean If `true` use `sep` literally (passed to string.find) +---@param plain (boolean|nil) If `true` use `sep` literally (passed to string.find) ---@return fun():string (function) Iterator over the split components function vim.gsplit(s, sep, plain) vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } }) @@ -108,11 +108,9 @@ end --- ---@see |vim.gsplit()| --- ----@alias split_kwargs {plain: boolean, trimempty: boolean} | boolean | nil ---- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs? {plain: boolean, trimempty: boolean} (table|nil) Keyword arguments: +---@param kwargs ({plain: boolean, trimempty: boolean}|nil) Keyword arguments: --- - plain: (boolean) If `true` use `sep` literally (passed to string.find) --- - trimempty: (boolean) If `true` remove empty items from the front --- and back of the list @@ -159,8 +157,8 @@ end --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@param t table<T, any> (table) Table ---@generic T: table +---@param t table<T, any> (table) Table ---@return T[] (list) List of keys function vim.tbl_keys(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -417,8 +415,8 @@ end ---@generic T: table ---@param dst T List which will be modified and appended to ---@param src table List from which values will be inserted ----@param start? number Start index on src. Defaults to 1 ----@param finish? number Final index on src. Defaults to `#src` +---@param start (number|nil) Start index on src. Defaults to 1 +---@param finish (number|nil) Final index on src. Defaults to `#src` ---@return T dst function vim.list_extend(dst, src, start, finish) vim.validate({ @@ -486,7 +484,7 @@ function vim.tbl_islist(t) -- TODO(bfredl): in the future, we will always be inside nvim -- then this check can be deleted. if vim._empty_dict_mt == nil then - return nil + return false end return getmetatable(t) ~= vim._empty_dict_mt end @@ -544,7 +542,7 @@ end ---@return string %-escaped pattern string function vim.pesc(s) vim.validate({ s = { s, 's' } }) - return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') + return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) end --- Tests if `s` starts with `prefix`. diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 4995c80a02..c0a1eca0ce 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -1,5 +1,6 @@ local M = {} local ts = vim.treesitter +local health = require('vim.health') --- Lists the parsers currently installed --- @@ -10,27 +11,23 @@ end --- Performs a healthcheck for treesitter integration function M.check() - 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)) + health.report_info(string.format('Nvim 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)) + if not is_loadable or not ret then + health.report_error( + string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?') + ) 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) + health.report_ok( + string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) - else - report_error(string.format('Unable to load parser for %s', parsername)) end end end diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 4db5e20921..db21066819 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Oct 15 +" Last Change: 2022 Oct 28 " If there already is an option window, jump to that one. let buf = bufnr('option-window') @@ -20,7 +20,7 @@ let s:cpo_save = &cpo set cpo&vim " function to be called when <CR> is hit in the option-window -fun! <SID>CR() +func <SID>CR() " If on a continued comment line, go back to the first comment line let lnum = search("^[^\t]", 'bWcn') @@ -47,10 +47,10 @@ fun! <SID>CR() elseif match(line, '^ \=[0-9]') >= 0 exe "norm! /" . line . "\<CR>zt" endif -endfun +endfunc " function to be called when <Space> is hit in the option-window -fun! <SID>Space() +func <SID>Space() let lnum = line(".") let line = getline(lnum) @@ -67,16 +67,17 @@ fun! <SID>Space() endif endif -endfun +endfunc -let s:local_to_window = '(local to window)' -let s:local_to_buffer = '(local to buffer)' -let s:global_or_local = '(global or local to buffer)' +let s:local_to_window = gettext('(local to window)') +let s:local_to_buffer = gettext('(local to buffer)') +let s:global_or_local = gettext('(global or local to buffer)') " find the window in which the option applies " returns 0 for global option, 1 for local option, -1 for error -fun! <SID>Find(lnum) - if getline(a:lnum - 1) =~ "(local to" +func <SID>Find(lnum) + let line = getline(a:lnum - 1) + if line =~ s:local_to_window || line =~ s:local_to_buffer let local = 1 let thiswin = winnr() wincmd p @@ -95,10 +96,10 @@ fun! <SID>Find(lnum) let local = -1 endif return local -endfun +endfunc " Update a "set" line in the option window -fun! <SID>Update(lnum, line, local, thiswin) +func <SID>Update(lnum, line, local, thiswin) " get the new value of the option and update the option window line if match(a:line, "=") >= 0 let name = substitute(a:line, '^ \tset \([^=]*\)=.*', '\1', "") @@ -123,7 +124,7 @@ fun! <SID>Update(lnum, line, local, thiswin) endif endif set nomodified -endfun +endfunc " Reset 'title' and 'icon' to make it work faster. " Reset 'undolevels' to avoid undo'ing until the buffer is empty. @@ -149,13 +150,13 @@ exe $OPTWIN_CMD . ' new option-window' setlocal ts=15 tw=0 noro buftype=nofile " Insert help and a "set" command for each option. -call append(0, '" Each "set" line shows the current value of an option (on the left).') -call append(1, '" Hit <CR> on a "set" line to execute it.') -call append(2, '" A boolean option will be toggled.') -call append(3, '" For other options you can edit the value before hitting <CR>.') -call append(4, '" Hit <CR> on a help line to open a help window on this option.') -call append(5, '" Hit <CR> on an index line to jump there.') -call append(6, '" Hit <Space> on a "set" line to refresh it.') +call append(0, gettext('" Each "set" line shows the current value of an option (on the left).')) +call append(1, gettext('" Hit <Enter> on a "set" line to execute it.')) +call append(2, gettext('" A boolean option will be toggled.')) +call append(3, gettext('" For other options you can edit the value before hitting <Enter>.')) +call append(4, gettext('" Hit <Enter> on a help line to open a help window on this option.')) +call append(5, gettext('" Hit <Enter> on an index line to jump there.')) +call append(6, gettext('" Hit <Space> on a "set" line to refresh it.')) " These functions are called often below. Keep them fast! @@ -170,34 +171,34 @@ func <SID>AddOption(name, text) endfunc " Init a local binary option -fun! <SID>BinOptionL(name) +func <SID>BinOptionL(name) let val = getwinvar(winnr('#'), '&' . a:name) call append("$", substitute(substitute(" \tset " . val . a:name . "\t" . \!val . a:name, "0", "no", ""), "1", "", "")) -endfun +endfunc " Init a global binary option -fun! <SID>BinOptionG(name, val) +func <SID>BinOptionG(name, val) call append("$", substitute(substitute(" \tset " . a:val . a:name . "\t" . \!a:val . a:name, "0", "no", ""), "1", "", "")) -endfun +endfunc " Init a local string option -fun! <SID>OptionL(name) +func <SID>OptionL(name) let val = escape(getwinvar(winnr('#'), '&' . a:name), " \t\\\"|") call append("$", " \tset " . a:name . "=" . val) -endfun +endfunc " Init a global string option -fun! <SID>OptionG(name, val) +func <SID>OptionG(name, val) call append("$", " \tset " . a:name . "=" . escape(a:val, " \t\\\"|")) -endfun +endfunc let s:idx = 1 let s:lnum = line("$") call append("$", "") -fun! <SID>Header(text) +func <SID>Header(text) let line = s:idx . " " . a:text if s:idx < 10 let line = " " . line @@ -208,15 +209,15 @@ fun! <SID>Header(text) call append(s:lnum, line) let s:idx = s:idx + 1 let s:lnum = s:lnum + 1 -endfun +endfunc " Get the value of 'pastetoggle'. It could be a special key. -fun! <SID>PTvalue() +func <SID>PTvalue() redir @a silent set pt redir END return substitute(@a, '[^=]*=\(.*\)', '\1', "") -endfun +endfunc " Restore the previous value of 'cpoptions' here, it's used below. let &cpo = s:cpo_save @@ -224,1042 +225,1035 @@ let &cpo = s:cpo_save " List of all options, organized by function. " The text should be sufficient to know what the option is used for. -call <SID>Header("important") -call append("$", "compatible\tbehave very Vi compatible (not advisable)") +call <SID>Header(gettext("important")) +call <SID>AddOption("compatible", gettext("behave very Vi compatible (not advisable)")) call <SID>BinOptionG("cp", &cp) -call append("$", "cpoptions\tlist of flags to specify Vi compatibility") +call <SID>AddOption("cpoptions", gettext("list of flags to specify Vi compatibility")) call <SID>OptionG("cpo", &cpo) -call append("$", "paste\tpaste mode, insert typed text literally") +call <SID>AddOption("paste", gettext("paste mode, insert typed text literally")) call <SID>BinOptionG("paste", &paste) -call append("$", "pastetoggle\tkey sequence to toggle paste mode") +call <SID>AddOption("pastetoggle", gettext("key sequence to toggle paste mode")) if &pt =~ "\x80" call append("$", " \tset pt=" . <SID>PTvalue()) else call <SID>OptionG("pt", &pt) endif -call append("$", "runtimepath\tlist of directories used for runtime files and plugins") +call <SID>AddOption("runtimepath", gettext("list of directories used for runtime files and plugins")) call <SID>OptionG("rtp", &rtp) -call append("$", "packpath\tlist of directories used for plugin packages") +call <SID>AddOption("packpath", gettext("list of directories used for plugin packages")) call <SID>OptionG("pp", &pp) -call append("$", "helpfile\tname of the main help file") +call <SID>AddOption("helpfile", gettext("name of the main help file")) call <SID>OptionG("hf", &hf) -call <SID>Header("moving around, searching and patterns") -call append("$", "whichwrap\tlist of flags specifying which commands wrap to another line") -call append("$", "\t(local to window)") -call <SID>OptionL("ww") -call append("$", "startofline\tmany jump commands move the cursor to the first non-blank") -call append("$", "\tcharacter of a line") +call <SID>Header(gettext("moving around, searching and patterns")) +call <SID>AddOption("whichwrap", gettext("list of flags specifying which commands wrap to another line")) +call <SID>OptionG("ww", &ww) +call <SID>AddOption("startofline", gettext("many jump commands move the cursor to the first non-blank\ncharacter of a line")) call <SID>BinOptionG("sol", &sol) -call append("$", "paragraphs\tnroff macro names that separate paragraphs") +call <SID>AddOption("paragraphs", gettext("nroff macro names that separate paragraphs")) call <SID>OptionG("para", ¶) -call append("$", "sections\tnroff macro names that separate sections") +call <SID>AddOption("sections", gettext("nroff macro names that separate sections")) call <SID>OptionG("sect", §) -call append("$", "path\tlist of directory names used for file searching") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("path", gettext("list of directory names used for file searching")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("pa", &pa) -call <SID>AddOption("cdhome", ":cd without argument goes to the home directory") +call <SID>AddOption("cdhome", gettext(":cd without argument goes to the home directory")) call <SID>BinOptionG("cdh", &cdh) -call append("$", "cdpath\tlist of directory names used for :cd") +call <SID>AddOption("cdpath", gettext("list of directory names used for :cd")) call <SID>OptionG("cd", &cd) if exists("+autochdir") - call append("$", "autochdir\tchange to directory of file in buffer") + call <SID>AddOption("autochdir", gettext("change to directory of file in buffer")) call <SID>BinOptionG("acd", &acd) endif -call append("$", "wrapscan\tsearch commands wrap around the end of the buffer") +call <SID>AddOption("wrapscan", gettext("search commands wrap around the end of the buffer")) call <SID>BinOptionG("ws", &ws) -call append("$", "incsearch\tshow match for partly typed search command") +call <SID>AddOption("incsearch", gettext("show match for partly typed search command")) call <SID>BinOptionG("is", &is) -call append("$", "magic\tchange the way backslashes are used in search patterns") +call <SID>AddOption("magic", gettext("change the way backslashes are used in search patterns")) call <SID>BinOptionG("magic", &magic) -call append("$", "regexpengine\tselect the default regexp engine used") +call <SID>AddOption("regexpengine", gettext("select the default regexp engine used")) call <SID>OptionG("re", &re) -call append("$", "ignorecase\tignore case when using a search pattern") +call <SID>AddOption("ignorecase", gettext("ignore case when using a search pattern")) call <SID>BinOptionG("ic", &ic) -call append("$", "smartcase\toverride 'ignorecase' when pattern has upper case characters") +call <SID>AddOption("smartcase", gettext("override 'ignorecase' when pattern has upper case characters")) call <SID>BinOptionG("scs", &scs) -call append("$", "casemap\twhat method to use for changing case of letters") +call <SID>AddOption("casemap", gettext("what method to use for changing case of letters")) call <SID>OptionG("cmp", &cmp) -call append("$", "maxmempattern\tmaximum amount of memory in Kbyte used for pattern matching") +call <SID>AddOption("maxmempattern", gettext("maximum amount of memory in Kbyte used for pattern matching")) call append("$", " \tset mmp=" . &mmp) -call append("$", "define\tpattern for a macro definition line") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("define", gettext("pattern for a macro definition line")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("def", &def) if has("find_in_path") - call append("$", "include\tpattern for an include-file line") - call append("$", "\t(local to buffer)") + call <SID>AddOption("include", gettext("pattern for an include-file line")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inc") - call append("$", "includeexpr\texpression used to transform an include line to a file name") - call append("$", "\t(local to buffer)") + call <SID>AddOption("includeexpr", gettext("expression used to transform an include line to a file name")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inex") endif -call <SID>Header("tags") -call append("$", "tagbsearch\tuse binary searching in tags files") +call <SID>Header(gettext("tags")) +call <SID>AddOption("tagbsearch", gettext("use binary searching in tags files")) call <SID>BinOptionG("tbs", &tbs) -call append("$", "taglength\tnumber of significant characters in a tag name or zero") +call <SID>AddOption("taglength", gettext("number of significant characters in a tag name or zero")) call append("$", " \tset tl=" . &tl) -call append("$", "tags\tlist of file names to search for tags") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("tags", gettext("list of file names to search for tags")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tag", &tag) -call append("$", "tagcase\thow to handle case when searching in tags files:") -call append("$", "\t\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("tagcase", gettext("how to handle case when searching in tags files:\n\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tc", &tc) -call append("$", "tagrelative\tfile names in a tags file are relative to the tags file") +call <SID>AddOption("tagrelative", gettext("file names in a tags file are relative to the tags file")) call <SID>BinOptionG("tr", &tr) -call append("$", "tagstack\ta :tag command will use the tagstack") +call <SID>AddOption("tagstack", gettext("a :tag command will use the tagstack")) call <SID>BinOptionG("tgst", &tgst) -call append("$", "showfulltag\twhen completing tags in Insert mode show more info") +call <SID>AddOption("showfulltag", gettext("when completing tags in Insert mode show more info")) call <SID>BinOptionG("sft", &sft) if has("eval") - call append("$", "tagfunc\ta function to be used to perform tag searches") - call append("$", "\t(local to buffer)") + call <SID>AddOption("tagfunc", gettext("a function to be used to perform tag searches")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("tfu") endif -call <SID>Header("displaying text") -call append("$", "scroll\tnumber of lines to scroll for CTRL-U and CTRL-D") -call append("$", "\t(local to window)") +call <SID>Header(gettext("displaying text")) +call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scr") -call append("$", "scrolloff\tnumber of screen lines to show around the cursor") +call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor")) call append("$", " \tset so=" . &so) -call append("$", "wrap\tlong lines wrap") -call append("$", "\t(local to window)") +call <SID>AddOption("wrap", gettext("long lines wrap")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wrap") -call append("$", "linebreak\twrap long lines at a character in 'breakat'") -call append("$", "\t(local to window)") +call <SID>AddOption("linebreak", gettext("wrap long lines at a character in 'breakat'")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("lbr") -call append("$", "breakindent\tpreserve indentation in wrapped text") -call append("$", "\t(local to window)") +call <SID>AddOption("breakindent", gettext("preserve indentation in wrapped text")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("bri") -call append("$", "breakindentopt\tadjust breakindent behaviour") -call append("$", "\t(local to window)") +call <SID>AddOption("breakindentopt", gettext("adjust breakindent behaviour")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("briopt") -call append("$", "breakat\twhich characters might cause a line break") +call <SID>AddOption("breakat", gettext("which characters might cause a line break")) call <SID>OptionG("brk", &brk) -call append("$", "showbreak\tstring to put before wrapped screen lines") +call <SID>AddOption("showbreak", gettext("string to put before wrapped screen lines")) call <SID>OptionG("sbr", &sbr) -call append("$", "sidescroll\tminimal number of columns to scroll horizontally") +call <SID>AddOption("sidescroll", gettext("minimal number of columns to scroll horizontally")) call append("$", " \tset ss=" . &ss) -call append("$", "sidescrolloff\tminimal number of columns to keep left and right of the cursor") +call <SID>AddOption("sidescrolloff", gettext("minimal number of columns to keep left and right of the cursor")) call append("$", " \tset siso=" . &siso) -call append("$", "display\tinclude \"lastline\" to show the last line even if it doesn't fit") -call append("$", "\tinclude \"uhex\" to show unprintable characters as a hex number") +call <SID>AddOption("display", gettext("include \"lastline\" to show the last line even if it doesn't fit\ninclude \"uhex\" to show unprintable characters as a hex number")) call <SID>OptionG("dy", &dy) -call append("$", "fillchars\tcharacters to use for the status line, folds and filler lines") +call <SID>AddOption("fillchars", gettext("characters to use for the status line, folds and filler lines")) call <SID>OptionG("fcs", &fcs) -call append("$", "cmdheight\tnumber of lines used for the command-line") +call <SID>AddOption("cmdheight", gettext("number of lines used for the command-line")) call append("$", " \tset ch=" . &ch) -call append("$", "columns\twidth of the display") +call <SID>AddOption("columns", gettext("width of the display")) call append("$", " \tset co=" . &co) -call append("$", "lines\tnumber of lines in the display") +call <SID>AddOption("lines", gettext("number of lines in the display")) call append("$", " \tset lines=" . &lines) -call append("$", "window\tnumber of lines to scroll for CTRL-F and CTRL-B") +call <SID>AddOption("window", gettext("number of lines to scroll for CTRL-F and CTRL-B")) call append("$", " \tset window=" . &window) -call append("$", "lazyredraw\tdon't redraw while executing macros") +call <SID>AddOption("lazyredraw", gettext("don't redraw while executing macros")) call <SID>BinOptionG("lz", &lz) if has("reltime") - call append("$", "redrawtime\ttimeout for 'hlsearch' and :match highlighting in msec") + call <SID>AddOption("redrawtime", gettext("timeout for 'hlsearch' and :match highlighting in msec")) call append("$", " \tset rdt=" . &rdt) endif -call append("$", "writedelay\tdelay in msec for each char written to the display") -call append("$", "\t(for debugging)") +call <SID>AddOption("writedelay", gettext("delay in msec for each char written to the display\n(for debugging)")) call append("$", " \tset wd=" . &wd) -call append("$", "list\tshow <Tab> as ^I and end-of-line as $") -call append("$", "\t(local to window)") +call <SID>AddOption("list", gettext("show <Tab> as ^I and end-of-line as $")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("list") -call append("$", "listchars\tlist of strings used for list mode") +call <SID>AddOption("listchars", gettext("list of strings used for list mode")) call <SID>OptionG("lcs", &lcs) -call append("$", "number\tshow the line number for each line") -call append("$", "\t(local to window)") +call <SID>AddOption("number", gettext("show the line number for each line")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("nu") -call append("$", "relativenumber\tshow the relative line number for each line") -call append("$", "\t(local to window)") +call <SID>AddOption("relativenumber", gettext("show the relative line number for each line")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("rnu") if has("linebreak") - call append("$", "numberwidth\tnumber of columns to use for the line number") - call append("$", "\t(local to window)") + call <SID>AddOption("numberwidth", gettext("number of columns to use for the line number")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("nuw") endif if has("conceal") - call append("$", "conceallevel\tcontrols whether concealable text is hidden") - call append("$", "\t(local to window)") + call <SID>AddOption("conceallevel", gettext("controls whether concealable text is hidden")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cole") - call append("$", "concealcursor\tmodes in which text in the cursor line can be concealed") - call append("$", "\t(local to window)") + call <SID>AddOption("concealcursor", gettext("modes in which text in the cursor line can be concealed")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cocu") endif -call <SID>Header("syntax, highlighting and spelling") -call append("$", "background\t\"dark\" or \"light\"; the background color brightness") +call <SID>Header(gettext("syntax, highlighting and spelling")) +call <SID>AddOption("background", gettext("\"dark\" or \"light\"; the background color brightness")) call <SID>OptionG("bg", &bg) -call append("$", "filetype\ttype of file; triggers the FileType event when set") -call append("$", "\t(local to buffer)") +call <SID>AddOption("filetype", gettext("type of file; triggers the FileType event when set")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ft") if has("syntax") - call append("$", "syntax\tname of syntax highlighting used") - call append("$", "\t(local to buffer)") + call <SID>AddOption("syntax", gettext("name of syntax highlighting used")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("syn") - call append("$", "synmaxcol\tmaximum column to look for syntax items") - call append("$", "\t(local to buffer)") + call <SID>AddOption("synmaxcol", gettext("maximum column to look for syntax items")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("smc") endif -call append("$", "highlight\twhich highlighting to use for various occasions") +call <SID>AddOption("highlight", gettext("which highlighting to use for various occasions")) call <SID>OptionG("hl", &hl) -call append("$", "hlsearch\thighlight all matches for the last used search pattern") +call <SID>AddOption("hlsearch", gettext("highlight all matches for the last used search pattern")) call <SID>BinOptionG("hls", &hls) if has("termguicolors") - call append("$", "termguicolors\tuse GUI colors for the terminal") + call <SID>AddOption("termguicolors", gettext("use GUI colors for the terminal")) call <SID>BinOptionG("tgc", &tgc) endif if has("syntax") - call append("$", "cursorcolumn\thighlight the screen column of the cursor") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorcolumn", gettext("highlight the screen column of the cursor")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("cuc") - call append("$", "cursorline\thighlight the screen line of the cursor") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorline", gettext("highlight the screen line of the cursor")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("cul") - call append("$", "cursorlineopt\tspecifies which area 'cursorline' highlights") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorlineopt", gettext("specifies which area 'cursorline' highlights")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("culopt") - call append("$", "colorcolumn\tcolumns to highlight") - call append("$", "\t(local to window)") + call <SID>AddOption("colorcolumn", gettext("columns to highlight")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cc") - call append("$", "spell\thighlight spelling mistakes") - call append("$", "\t(local to window)") + call <SID>AddOption("spell", gettext("highlight spelling mistakes")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("spell") - call append("$", "spelllang\tlist of accepted languages") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spelllang", gettext("list of accepted languages")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spl") - call append("$", "spellfile\tfile that \"zg\" adds good words to") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spellfile", gettext("file that \"zg\" adds good words to")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spf") - call append("$", "spellcapcheck\tpattern to locate the end of a sentence") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spellcapcheck", gettext("pattern to locate the end of a sentence")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spc") - call append("$", "spelloptions\tflags to change how spell checking works") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spelloptions", gettext("flags to change how spell checking works")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spo") - call append("$", "spellsuggest\tmethods used to suggest corrections") + call <SID>AddOption("spellsuggest", gettext("methods used to suggest corrections")) call <SID>OptionG("sps", &sps) - call append("$", "mkspellmem\tamount of memory used by :mkspell before compressing") + call <SID>AddOption("mkspellmem", gettext("amount of memory used by :mkspell before compressing")) call <SID>OptionG("msm", &msm) endif -call <SID>Header("multiple windows") -call append("$", "laststatus\t0, 1, 2 or 3; when to use a status line for the last window") +call <SID>Header(gettext("multiple windows")) +call <SID>AddOption("laststatus", gettext("0, 1, 2 or 3; when to use a status line for the last window")) call append("$", " \tset ls=" . &ls) if has("statusline") - call append("$", "statusline\talternate format to be used for a status line") + call <SID>AddOption("statusline", gettext("alternate format to be used for a status line")) call <SID>OptionG("stl", &stl) endif -call append("$", "equalalways\tmake all windows the same size when adding/removing windows") +call <SID>AddOption("equalalways", gettext("make all windows the same size when adding/removing windows")) call <SID>BinOptionG("ea", &ea) -call append("$", "eadirection\tin which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"") +call <SID>AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"")) call <SID>OptionG("ead", &ead) -call append("$", "winheight\tminimal number of lines used for the current window") +call <SID>AddOption("winheight", gettext("minimal number of lines used for the current window")) call append("$", " \tset wh=" . &wh) -call append("$", "winminheight\tminimal number of lines used for any window") +call <SID>AddOption("winminheight", gettext("minimal number of lines used for any window")) call append("$", " \tset wmh=" . &wmh) -call append("$", "winfixheight\tkeep the height of the window") -call append("$", "\t(local to window)") +call <SID>AddOption("winfixheight", gettext("keep the height of the window")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wfh") -call append("$", "winfixwidth\tkeep the width of the window") -call append("$", "\t(local to window)") +call <SID>AddOption("winfixwidth", gettext("keep the width of the window")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wfw") -call append("$", "winwidth\tminimal number of columns used for the current window") +call <SID>AddOption("winwidth", gettext("minimal number of columns used for the current window")) call append("$", " \tset wiw=" . &wiw) -call append("$", "winminwidth\tminimal number of columns used for any window") +call <SID>AddOption("winminwidth", gettext("minimal number of columns used for any window")) call append("$", " \tset wmw=" . &wmw) -call append("$", "helpheight\tinitial height of the help window") +call <SID>AddOption("helpheight", gettext("initial height of the help window")) call append("$", " \tset hh=" . &hh) if has("quickfix") - " call append("$", "previewpopup\tuse a popup window for preview") + " call <SID>AddOption("previewpopup", gettext("use a popup window for preview")) " call append("$", " \tset pvp=" . &pvp) - call append("$", "previewheight\tdefault height for the preview window") + call <SID>AddOption("previewheight", gettext("default height for the preview window")) call append("$", " \tset pvh=" . &pvh) - call append("$", "previewwindow\tidentifies the preview window") - call append("$", "\t(local to window)") + call <SID>AddOption("previewwindow", gettext("identifies the preview window")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("pvw") endif -call append("$", "hidden\tdon't unload a buffer when no longer shown in a window") +call <SID>AddOption("hidden", gettext("don't unload a buffer when no longer shown in a window")) call <SID>BinOptionG("hid", &hid) -call append("$", "switchbuf\t\"useopen\" and/or \"split\"; which window to use when jumping") -call append("$", "\tto a buffer") +call <SID>AddOption("switchbuf", gettext("\"useopen\" and/or \"split\"; which window to use when jumping\nto a buffer")) call <SID>OptionG("swb", &swb) -call append("$", "splitbelow\ta new window is put below the current one") +call <SID>AddOption("splitbelow", gettext("a new window is put below the current one")) call <SID>BinOptionG("sb", &sb) -call append("$", "splitkeep\ta determines scroll behavior for split windows") +call <SID>AddOption("splitkeep", gettext("determines scroll behavior for split windows")) call <SID>BinOptionG("spk", &spk) -call append("$", "splitright\ta new window is put right of the current one") +call <SID>AddOption("splitright", gettext("a new window is put right of the current one")) call <SID>BinOptionG("spr", &spr) -call append("$", "scrollbind\tthis window scrolls together with other bound windows") -call append("$", "\t(local to window)") +call <SID>AddOption("scrollbind", gettext("this window scrolls together with other bound windows")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("scb") -call append("$", "scrollopt\t\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'") +call <SID>AddOption("scrollopt", gettext("\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'")) call <SID>OptionG("sbo", &sbo) -call append("$", "cursorbind\tthis window's cursor moves together with other bound windows") -call append("$", "\t(local to window)") +call <SID>AddOption("cursorbind", gettext("this window's cursor moves together with other bound windows")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("crb") if has("terminal") - call append("$", "termsize\tsize of a terminal window") - call append("$", "\t(local to window)") + call <SID>AddOption("termsize", gettext("size of a terminal window")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("tms") - call append("$", "termkey\tkey that precedes Vim commands in a terminal window") - call append("$", "\t(local to window)") + call <SID>AddOption("termkey", gettext("key that precedes Vim commands in a terminal window")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("tk") endif -call <SID>Header("multiple tab pages") -call append("$", "showtabline\t0, 1 or 2; when to use a tab pages line") +call <SID>Header(gettext("multiple tab pages")) +call <SID>AddOption("showtabline", gettext("0, 1 or 2; when to use a tab pages line")) call append("$", " \tset stal=" . &stal) -call append("$", "tabpagemax\tmaximum number of tab pages to open for -p and \"tab all\"") +call <SID>AddOption("tabpagemax", gettext("maximum number of tab pages to open for -p and \"tab all\"")) call append("$", " \tset tpm=" . &tpm) -call append("$", "tabline\tcustom tab pages line") +call <SID>AddOption("tabline", gettext("custom tab pages line")) call <SID>OptionG("tal", &tal) if has("gui") - call append("$", "guitablabel\tcustom tab page label for the GUI") + call <SID>AddOption("guitablabel", gettext("custom tab page label for the GUI")) call <SID>OptionG("gtl", >l) - call append("$", "guitabtooltip\tcustom tab page tooltip for the GUI") + call <SID>AddOption("guitabtooltip", gettext("custom tab page tooltip for the GUI")) call <SID>OptionG("gtt", >t) endif -call <SID>Header("terminal") -call append("$", "scrolljump\tminimal number of lines to scroll at a time") +call <SID>Header(gettext("terminal")) +call <SID>AddOption("scrolljump", gettext("minimal number of lines to scroll at a time")) call append("$", " \tset sj=" . &sj) if has("gui") || has("msdos") || has("win32") - call append("$", "guicursor\tspecifies what the cursor looks like in different modes") + call <SID>AddOption("guicursor", gettext("specifies what the cursor looks like in different modes")) call <SID>OptionG("gcr", &gcr) endif if has("title") let &title = s:old_title - call append("$", "title\tshow info in the window title") + call <SID>AddOption("title", gettext("show info in the window title")) call <SID>BinOptionG("title", &title) set notitle - call append("$", "titlelen\tpercentage of 'columns' used for the window title") + call <SID>AddOption("titlelen", gettext("percentage of 'columns' used for the window title")) call append("$", " \tset titlelen=" . &titlelen) - call append("$", "titlestring\twhen not empty, string to be used for the window title") + call <SID>AddOption("titlestring", gettext("when not empty, string to be used for the window title")) call <SID>OptionG("titlestring", &titlestring) - call append("$", "titleold\tstring to restore the title to when exiting Vim") + call <SID>AddOption("titleold", gettext("string to restore the title to when exiting Vim")) call <SID>OptionG("titleold", &titleold) let &icon = s:old_icon - call append("$", "icon\tset the text of the icon for this window") + call <SID>AddOption("icon", gettext("set the text of the icon for this window")) call <SID>BinOptionG("icon", &icon) set noicon - call append("$", "iconstring\twhen not empty, text for the icon of this window") + call <SID>AddOption("iconstring", gettext("when not empty, text for the icon of this window")) call <SID>OptionG("iconstring", &iconstring) endif -call <SID>Header("using the mouse") -call append("$", "mouse\tlist of flags for using the mouse") +call <SID>Header(gettext("using the mouse")) +call <SID>AddOption("mouse", gettext("list of flags for using the mouse")) call <SID>OptionG("mouse", &mouse) if has("gui") - call append("$", "mousefocus\tthe window with the mouse pointer becomes the current one") + call <SID>AddOption("mousefocus", gettext("the window with the mouse pointer becomes the current one")) call <SID>BinOptionG("mousef", &mousef) - call append("$", "mousehide\thide the mouse pointer while typing") + call <SID>AddOption("mousehide", gettext("hide the mouse pointer while typing")) call <SID>BinOptionG("mh", &mh) endif -call append("$", "mousemodel\t\"extend\", \"popup\" or \"popup_setpos\"; what the right") -call append("$", "\tmouse button is used for") +call <SID>AddOption("mousemodel", gettext("\"extend\", \"popup\" or \"popup_setpos\"; what the right\nmouse button is used for")) call <SID>OptionG("mousem", &mousem) -call append("$", "mousetime\tmaximum time in msec to recognize a double-click") +call <SID>AddOption("mousetime", gettext("maximum time in msec to recognize a double-click")) call append("$", " \tset mouset=" . &mouset) if has("mouseshape") - call append("$", "mouseshape\twhat the mouse pointer looks like in different modes") + call <SID>AddOption("mouseshape", gettext("what the mouse pointer looks like in different modes")) call <SID>OptionG("mouses", &mouses) endif if has("gui") - call <SID>Header("GUI") - call append("$", "guifont\tlist of font names to be used in the GUI") + call <SID>Header(gettext("GUI")) + call <SID>AddOption("guifont", gettext("list of font names to be used in the GUI")) call <SID>OptionG("gfn", &gfn) if has("xfontset") - call append("$", "guifontset\tpair of fonts to be used, for multibyte editing") + call <SID>AddOption("guifontset", gettext("pair of fonts to be used, for multibyte editing")) call <SID>OptionG("gfs", &gfs) endif - call append("$", "guifontwide\tlist of font names to be used for double-wide characters") + call <SID>AddOption("guifontwide", gettext("list of font names to be used for double-wide characters")) call <SID>OptionG("gfw", &gfw) - call append("$", "guioptions\tlist of flags that specify how the GUI works") + call <SID>AddOption("guioptions", gettext("list of flags that specify how the GUI works")) call <SID>OptionG("go", &go) if has("gui_gtk") - call append("$", "toolbar\t\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar") + call <SID>AddOption("toolbar", gettext("\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar")) call <SID>OptionG("tb", &tb) if has("gui_gtk2") - call append("$", "toolbariconsize\tsize of toolbar icons") + call <SID>AddOption("toolbariconsize", gettext("size of toolbar icons")) call <SID>OptionG("tbis", &tbis) endif endif if has("browse") - call append("$", "browsedir\t\"last\", \"buffer\" or \"current\": which directory used for the file browser") + call <SID>AddOption("browsedir", gettext("\"last\", \"buffer\" or \"current\": which directory used for the file browser")) call <SID>OptionG("bsdir", &bsdir) endif if has("multi_lang") - call append("$", "langmenu\tlanguage to be used for the menus") + call <SID>AddOption("langmenu", gettext("language to be used for the menus")) call <SID>OptionG("langmenu", &lm) endif - call append("$", "menuitems\tmaximum number of items in one menu") + call <SID>AddOption("menuitems", gettext("maximum number of items in one menu")) call append("$", " \tset mis=" . &mis) if has("winaltkeys") - call append("$", "winaltkeys\t\"no\", \"yes\" or \"menu\"; how to use the ALT key") + call <SID>AddOption("winaltkeys", gettext("\"no\", \"yes\" or \"menu\"; how to use the ALT key")) call <SID>OptionG("wak", &wak) endif - call append("$", "linespace\tnumber of pixel lines to use between characters") + call <SID>AddOption("linespace", gettext("number of pixel lines to use between characters")) call append("$", " \tset lsp=" . &lsp) if has("balloon_eval") || has("balloon_eval_term") - call append("$", "balloondelay\tdelay in milliseconds before a balloon may pop up") + call <SID>AddOption("balloondelay", gettext("delay in milliseconds before a balloon may pop up")) call append("$", " \tset bdlay=" . &bdlay) if has("balloon_eval") - call append("$", "ballooneval\tuse balloon evaluation in the GUI") + call <SID>AddOption("ballooneval", gettext("use balloon evaluation in the GUI")) call <SID>BinOptionG("beval", &beval) endif if has("balloon_eval_term") - call append("$", "balloonevalterm\tuse balloon evaluation in the terminal") + call <SID>AddOption("balloonevalterm", gettext("use balloon evaluation in the terminal")) call <SID>BinOptionG("bevalterm", &beval) endif if has("eval") - call append("$", "balloonexpr\texpression to show in balloon eval") + call <SID>AddOption("balloonexpr", gettext("expression to show in balloon eval")) call append("$", " \tset bexpr=" . &bexpr) endif endif endif if has("printer") - call <SID>Header("printing") - call append("$", "printoptions\tlist of items that control the format of :hardcopy output") + call <SID>Header(gettext("printing")) + call <SID>AddOption("printoptions", gettext("list of items that control the format of :hardcopy output")) call <SID>OptionG("popt", &popt) - call append("$", "printdevice\tname of the printer to be used for :hardcopy") + call <SID>AddOption("printdevice", gettext("name of the printer to be used for :hardcopy")) call <SID>OptionG("pdev", &pdev) if has("postscript") - call append("$", "printexpr\texpression used to print the PostScript file for :hardcopy") + call <SID>AddOption("printexpr", gettext("expression used to print the PostScript file for :hardcopy")) call <SID>OptionG("pexpr", &pexpr) endif - call append("$", "printfont\tname of the font to be used for :hardcopy") + call <SID>AddOption("printfont", gettext("name of the font to be used for :hardcopy")) call <SID>OptionG("pfn", &pfn) - call append("$", "printheader\tformat of the header used for :hardcopy") + call <SID>AddOption("printheader", gettext("format of the header used for :hardcopy")) call <SID>OptionG("pheader", &pheader) if has("postscript") - call append("$", "printencoding\tencoding used to print the PostScript file for :hardcopy") + call <SID>AddOption("printencoding", gettext("encoding used to print the PostScript file for :hardcopy")) call <SID>OptionG("penc", &penc) endif - call append("$", "printmbcharset\tthe CJK character set to be used for CJK output from :hardcopy") + call <SID>AddOption("printmbcharset", gettext("the CJK character set to be used for CJK output from :hardcopy")) call <SID>OptionG("pmbcs", &pmbcs) - call append("$", "printmbfont\tlist of font names to be used for CJK output from :hardcopy") + call <SID>AddOption("printmbfont", gettext("list of font names to be used for CJK output from :hardcopy")) call <SID>OptionG("pmbfn", &pmbfn) endif -call <SID>Header("messages and info") -call append("$", "terse\tadd 's' flag in 'shortmess' (don't show search message)") +call <SID>Header(gettext("messages and info")) +call <SID>AddOption("terse", gettext("add 's' flag in 'shortmess' (don't show search message)")) call <SID>BinOptionG("terse", &terse) -call append("$", "shortmess\tlist of flags to make messages shorter") +call <SID>AddOption("shortmess", gettext("list of flags to make messages shorter")) call <SID>OptionG("shm", &shm) -call append("$", "showcmd\tshow (partial) command keys in the status line") +call <SID>AddOption("showcmd", gettext("show (partial) command keys in the status line")) let &sc = s:old_sc call <SID>BinOptionG("sc", &sc) set nosc -call append("$", "showmode\tdisplay the current mode in the status line") +call <SID>AddOption("showmode", gettext("display the current mode in the status line")) call <SID>BinOptionG("smd", &smd) -call append("$", "ruler\tshow cursor position below each window") +call <SID>AddOption("ruler", gettext("show cursor position below each window")) let &ru = s:old_ru call <SID>BinOptionG("ru", &ru) set noru if has("statusline") - call append("$", "rulerformat\talternate format to be used for the ruler") + call <SID>AddOption("rulerformat", gettext("alternate format to be used for the ruler")) call <SID>OptionG("ruf", &ruf) endif -call append("$", "report\tthreshold for reporting number of changed lines") +call <SID>AddOption("report", gettext("threshold for reporting number of changed lines")) call append("$", " \tset report=" . &report) -call append("$", "verbose\tthe higher the more messages are given") +call <SID>AddOption("verbose", gettext("the higher the more messages are given")) call append("$", " \tset vbs=" . &vbs) -call append("$", "verbosefile\tfile to write messages in") +call <SID>AddOption("verbosefile", gettext("file to write messages in")) call <SID>OptionG("vfile", &vfile) -call append("$", "more\tpause listings when the screen is full") +call <SID>AddOption("more", gettext("pause listings when the screen is full")) call <SID>BinOptionG("more", &more) if has("dialog_con") || has("dialog_gui") - call append("$", "confirm\tstart a dialog when a command fails") + call <SID>AddOption("confirm", gettext("start a dialog when a command fails")) call <SID>BinOptionG("cf", &cf) endif -call append("$", "errorbells\tring the bell for error messages") +call <SID>AddOption("errorbells", gettext("ring the bell for error messages")) call <SID>BinOptionG("eb", &eb) -call append("$", "visualbell\tuse a visual bell instead of beeping") +call <SID>AddOption("visualbell", gettext("use a visual bell instead of beeping")) call <SID>BinOptionG("vb", &vb) -call append("$", "belloff\tdo not ring the bell for these reasons") +call <SID>AddOption("belloff", gettext("do not ring the bell for these reasons")) call <SID>OptionG("belloff", &belloff) if has("multi_lang") - call append("$", "helplang\tlist of preferred languages for finding help") + call <SID>AddOption("helplang", gettext("list of preferred languages for finding help")) call <SID>OptionG("hlg", &hlg) endif -call <SID>Header("selecting text") -call append("$", "selection\t\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves") +call <SID>Header(gettext("selecting text")) +call <SID>AddOption("selection", gettext("\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves")) call <SID>OptionG("sel", &sel) -call append("$", "selectmode\t\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode") -call append("$", "\tinstead of Visual mode") +call <SID>AddOption("selectmode", gettext("\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\ninstead of Visual mode")) call <SID>OptionG("slm", &slm) if has("clipboard") - call append("$", "clipboard\t\"unnamed\" to use the * register like unnamed register") - call append("$", "\t\"autoselect\" to always put selected text on the clipboard") + call <SID>AddOption("clipboard", gettext("\"unnamed\" to use the * register like unnamed register\n\"autoselect\" to always put selected text on the clipboard")) call <SID>OptionG("cb", &cb) endif -call append("$", "keymodel\t\"startsel\" and/or \"stopsel\"; what special keys can do") +call <SID>AddOption("keymodel", gettext("\"startsel\" and/or \"stopsel\"; what special keys can do")) call <SID>OptionG("km", &km) -call <SID>Header("editing text") -call append("$", "undolevels\tmaximum number of changes that can be undone") -call append("$", "\t(global or local to buffer)") +call <SID>Header(gettext("editing text")) +call <SID>AddOption("undolevels", gettext("maximum number of changes that can be undone")) +call append("$", "\t" .. s:global_or_local) call append("$", " \tset ul=" . s:old_ul) -call append("$", "undofile\tautomatically save and restore undo history") +call <SID>AddOption("undofile", gettext("automatically save and restore undo history")) call <SID>BinOptionG("udf", &udf) -call append("$", "undodir\tlist of directories for undo files") +call <SID>AddOption("undodir", gettext("list of directories for undo files")) call <SID>OptionG("udir", &udir) -call append("$", "undoreload\tmaximum number lines to save for undo on a buffer reload") +call <SID>AddOption("undoreload", gettext("maximum number lines to save for undo on a buffer reload")) call append("$", " \tset ur=" . &ur) -call append("$", "modified\tchanges have been made and not written to a file") +call <SID>AddOption("modified", gettext("changes have been made and not written to a file")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("mod") -call append("$", "readonly\tbuffer is not to be written") +call <SID>AddOption("readonly", gettext("buffer is not to be written")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ro") -call <SID>AddOption("modifiable", "changes to the text are possible") +call <SID>AddOption("modifiable", gettext("changes to the text are possible")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ma") -call append("$", "textwidth\tline length above which to break a line") +call <SID>AddOption("textwidth", gettext("line length above which to break a line")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("tw") -call append("$", "wrapmargin\tmargin from the right in which to break a line") +call <SID>AddOption("wrapmargin", gettext("margin from the right in which to break a line")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("wm") -call append("$", "backspace\tspecifies what <BS>, CTRL-W, etc. can do in Insert mode") +call <SID>AddOption("backspace", gettext("specifies what <BS>, CTRL-W, etc. can do in Insert mode")) call append("$", " \tset bs=" . &bs) -call append("$", "comments\tdefinition of what comment lines look like") -call append("$", "\t(local to buffer)") +call <SID>AddOption("comments", gettext("definition of what comment lines look like")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("com") -call append("$", "formatoptions\tlist of flags that tell how automatic formatting works") -call append("$", "\t(local to buffer)") +call <SID>AddOption("formatoptions", gettext("list of flags that tell how automatic formatting works")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fo") -call append("$", "formatlistpat\tpattern to recognize a numbered list") -call append("$", "\t(local to buffer)") +call <SID>AddOption("formatlistpat", gettext("pattern to recognize a numbered list")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("flp") if has("eval") - call append("$", "formatexpr\texpression used for \"gq\" to format lines") - call append("$", "\t(local to buffer)") + call <SID>AddOption("formatexpr", gettext("expression used for \"gq\" to format lines")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fex") endif if has("insert_expand") - call append("$", "complete\tspecifies how Insert mode completion works for CTRL-N and CTRL-P") - call append("$", "\t(local to buffer)") + call <SID>AddOption("complete", gettext("specifies how Insert mode completion works for CTRL-N and CTRL-P")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cpt") - call append("$", "completeopt\twhether to use a popup menu for Insert mode completion") + call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion")) call <SID>OptionG("cot", &cot) - call append("$", "pumheight\tmaximum height of the popup menu") + call <SID>AddOption("pumheight", gettext("maximum height of the popup menu")) call <SID>OptionG("ph", &ph) - if exists("&pw") - call append("$", "pumwidth\tminimum width of the popup menu") - call <SID>OptionG("pw", &pw) - endif - call append("$", "completefunc\tuser defined function for Insert mode completion") - call append("$", "\t(local to buffer)") + call <SID>AddOption("pumwidth", gettext("minimum width of the popup menu")) + call <SID>OptionG("pw", &pw) + call <SID>AddOption("completefunc", gettext("user defined function for Insert mode completion")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cfu") - call append("$", "omnifunc\tfunction for filetype-specific Insert mode completion") - call append("$", "\t(local to buffer)") + call <SID>AddOption("omnifunc", gettext("function for filetype-specific Insert mode completion")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ofu") - call append("$", "dictionary\tlist of dictionary files for keyword completion") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("dictionary", gettext("list of dictionary files for keyword completion")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("dict", &dict) - call append("$", "thesaurus\tlist of thesaurus files for keyword completion") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("thesaurus", gettext("list of thesaurus files for keyword completion")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tsr", &tsr) + call <SID>AddOption("thesaurusfunc", gettext("function used for thesaurus completion")) + call append("$", "\t" .. s:global_or_local) + call <SID>OptionG("tsrfu", &tsrfu) endif -call append("$", "infercase\tadjust case of a keyword completion match") -call append("$", "\t(local to buffer)") +call <SID>AddOption("infercase", gettext("adjust case of a keyword completion match")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("inf") if has("digraphs") - call append("$", "digraph\tenable entering digraphs with c1 <BS> c2") + call <SID>AddOption("digraph", gettext("enable entering digraphs with c1 <BS> c2")) call <SID>BinOptionG("dg", &dg) endif -call append("$", "tildeop\tthe \"~\" command behaves like an operator") +call <SID>AddOption("tildeop", gettext("the \"~\" command behaves like an operator")) call <SID>BinOptionG("top", &top) -call <SID>AddOption("operatorfunc", "function called for the \"g@\" operator") +call <SID>AddOption("operatorfunc", gettext("function called for the \"g@\" operator")) call <SID>OptionG("opfunc", &opfunc) -call append("$", "showmatch\twhen inserting a bracket, briefly jump to its match") +call <SID>AddOption("showmatch", gettext("when inserting a bracket, briefly jump to its match")) call <SID>BinOptionG("sm", &sm) -call append("$", "matchtime\ttenth of a second to show a match for 'showmatch'") +call <SID>AddOption("matchtime", gettext("tenth of a second to show a match for 'showmatch'")) call append("$", " \tset mat=" . &mat) -call append("$", "matchpairs\tlist of pairs that match for the \"%\" command") +call <SID>AddOption("matchpairs", gettext("list of pairs that match for the \"%\" command")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("mps") -call append("$", "joinspaces\tuse two spaces after '.' when joining a line") +call <SID>AddOption("joinspaces", gettext("use two spaces after '.' when joining a line")) call <SID>BinOptionG("js", &js) -call <SID>AddOption("nrformats", "\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\nrecognized for CTRL-A and CTRL-X commands") +call <SID>AddOption("nrformats", gettext("\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\nrecognized for CTRL-A and CTRL-X commands")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("nf") -call <SID>Header("tabs and indenting") -call append("$", "tabstop\tnumber of spaces a <Tab> in the text stands for") -call append("$", "\t(local to buffer)") +call <SID>Header(gettext("tabs and indenting")) +call <SID>AddOption("tabstop", gettext("number of spaces a <Tab> in the text stands for")) +call append("$", "\t" .. s:local_to_buffer) 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>AddOption("shiftwidth", gettext("number of spaces used for each step of (auto)indent")) +call append("$", "\t" .. s: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>AddOption("vartabstop", gettext("list of number of spaces a tab counts for")) + call append("$", "\t" .. s: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>AddOption("varsofttabstop", gettext("list of number of spaces a soft tabsstop counts for")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("vsts") endif -call append("$", "smarttab\ta <Tab> in an indent inserts 'shiftwidth' spaces") +call <SID>AddOption("smarttab", gettext("a <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>") -call append("$", "\t(local to buffer)") +call <SID>AddOption("softtabstop", gettext("if non-zero, number of spaces to insert for a <Tab>")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("sts") -call append("$", "shiftround\tround to 'shiftwidth' for \"<<\" and \">>\"") +call <SID>AddOption("shiftround", gettext("round to 'shiftwidth' for \"<<\" and \">>\"")) call <SID>BinOptionG("sr", &sr) -call append("$", "expandtab\texpand <Tab> to spaces in Insert mode") -call append("$", "\t(local to buffer)") +call <SID>AddOption("expandtab", gettext("expand <Tab> to spaces in Insert mode")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("et") -call append("$", "autoindent\tautomatically set the indent of a new line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("autoindent", gettext("automatically set the indent of a new line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ai") if has("smartindent") - call append("$", "smartindent\tdo clever autoindenting") - call append("$", "\t(local to buffer)") + call <SID>AddOption("smartindent", gettext("do clever autoindenting")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("si") endif if has("cindent") - call append("$", "cindent\tenable specific indenting for C code") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cindent", gettext("enable specific indenting for C code")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("cin") - call append("$", "cinoptions\toptions for C-indenting") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinoptions", gettext("options for C-indenting")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cino") - call append("$", "cinkeys\tkeys that trigger C-indenting in Insert mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinkeys", gettext("keys that trigger C-indenting in Insert mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cink") - call append("$", "cinwords\tlist of words that cause more C-indent") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinwords", gettext("list of words that cause more C-indent")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cinw") - call append("$", "cinscopedecls\tlist of scope declaration names used by cino-g") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinscopedecls", gettext("list of scope declaration names used by cino-g")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cinsd") - call append("$", "indentexpr\texpression used to obtain the indent of a line") - call append("$", "\t(local to buffer)") + call <SID>AddOption("indentexpr", gettext("expression used to obtain the indent of a line")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inde") - call append("$", "indentkeys\tkeys that trigger indenting with 'indentexpr' in Insert mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("indentkeys", gettext("keys that trigger indenting with 'indentexpr' in Insert mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("indk") endif -call append("$", "copyindent\tcopy whitespace for indenting from previous line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("copyindent", gettext("copy whitespace for indenting from previous line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ci") -call append("$", "preserveindent\tpreserve kind of whitespace when changing indent") -call append("$", "\t(local to buffer)") +call <SID>AddOption("preserveindent", gettext("preserve kind of whitespace when changing indent")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("pi") if has("lispindent") - call append("$", "lisp\tenable lisp mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("lisp", gettext("enable lisp mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("lisp") - call append("$", "lispwords\twords that change how lisp indenting works") + call <SID>AddOption("lispwords", gettext("words that change how lisp indenting works")) call <SID>OptionL("lw") - call <SID>AddOption("lispoptions", "options for Lisp indenting") + call <SID>AddOption("lispoptions", gettext("options for Lisp indenting")) call <SID>OptionL("lop") endif if has("folding") - call <SID>Header("folding") - call <SID>AddOption("foldenable", "unset to display all folds open") + call <SID>Header(gettext("folding")) + call <SID>AddOption("foldenable", gettext("unset to display all folds open")) call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("fen") - call append("$", "foldlevel\tfolds with a level higher than this number will be closed") + call <SID>AddOption("foldlevel", gettext("folds with a level higher than this number will be closed")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdl") - call append("$", "foldlevelstart\tvalue for 'foldlevel' when starting to edit a file") + call <SID>AddOption("foldlevelstart", gettext("value for 'foldlevel' when starting to edit a file")) call append("$", " \tset fdls=" . &fdls) - call append("$", "foldcolumn\twidth of the column used to indicate folds") - call append("$", "\t(local to window)") + call <SID>AddOption("foldcolumn", gettext("width of the column used to indicate folds")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdc") - call append("$", "foldtext\texpression used to display the text of a closed fold") - call append("$", "\t(local to window)") + call <SID>AddOption("foldtext", gettext("expression used to display the text of a closed fold")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdt") - call append("$", "foldclose\tset to \"all\" to close a fold when the cursor leaves it") + call <SID>AddOption("foldclose", gettext("set to \"all\" to close a fold when the cursor leaves it")) call <SID>OptionG("fcl", &fcl) - call append("$", "foldopen\tspecifies for which commands a fold will be opened") + call <SID>AddOption("foldopen", gettext("specifies for which commands a fold will be opened")) call <SID>OptionG("fdo", &fdo) - call append("$", "foldminlines\tminimum number of screen lines for a fold to be closed") - call append("$", "\t(local to window)") + call <SID>AddOption("foldminlines", gettext("minimum number of screen lines for a fold to be closed")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fml") - call append("$", "commentstring\ttemplate for comments; used to put the marker in") + call <SID>AddOption("commentstring", gettext("template for comments; used to put the marker in")) call <SID>OptionL("cms") - call <SID>AddOption("foldmethod", "folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n\"syntax\" or \"diff\"") + call <SID>AddOption("foldmethod", gettext("folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n\"syntax\" or \"diff\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdm") - call append("$", "foldexpr\texpression used when 'foldmethod' is \"expr\"") + call <SID>AddOption("foldexpr", gettext("expression used when 'foldmethod' is \"expr\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fde") - call append("$", "foldignore\tused to ignore lines when 'foldmethod' is \"indent\"") + call <SID>AddOption("foldignore", gettext("used to ignore lines when 'foldmethod' is \"indent\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdi") - call append("$", "foldmarker\tmarkers used when 'foldmethod' is \"marker\"") + call <SID>AddOption("foldmarker", gettext("markers used when 'foldmethod' is \"marker\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fmr") - call append("$", "foldnestmax\tmaximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"") + call <SID>AddOption("foldnestmax", gettext("maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdn") endif if has("diff") - call <SID>Header("diff mode") - call append("$", "diff\tuse diff mode for the current window") - call append("$", "\t(local to window)") + call <SID>Header(gettext("diff mode")) + call <SID>AddOption("diff", gettext("use diff mode for the current window")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("diff") - call append("$", "diffopt\toptions for using diff mode") + call <SID>AddOption("diffopt", gettext("options for using diff mode")) call <SID>OptionG("dip", &dip) - call append("$", "diffexpr\texpression used to obtain a diff file") + call <SID>AddOption("diffexpr", gettext("expression used to obtain a diff file")) call <SID>OptionG("dex", &dex) - call append("$", "patchexpr\texpression used to patch a file") + call <SID>AddOption("patchexpr", gettext("expression used to patch a file")) call <SID>OptionG("pex", &pex) endif -call <SID>Header("mapping") -call append("$", "maxmapdepth\tmaximum depth of mapping") +call <SID>Header(gettext("mapping")) +call <SID>AddOption("maxmapdepth", gettext("maximum depth of mapping")) call append("$", " \tset mmd=" . &mmd) -call append("$", "remap\trecognize mappings in mapped keys") -call append("$", "timeout\tallow timing out halfway into a mapping") +call <SID>AddOption("timeout", gettext("allow timing out halfway into a mapping")) call <SID>BinOptionG("to", &to) -call append("$", "ttimeout\tallow timing out halfway into a key code") +call <SID>AddOption("ttimeout", gettext("allow timing out halfway into a key code")) call <SID>BinOptionG("ttimeout", &ttimeout) -call append("$", "timeoutlen\ttime in msec for 'timeout'") +call <SID>AddOption("timeoutlen", gettext("time in msec for 'timeout'")) call append("$", " \tset tm=" . &tm) -call append("$", "ttimeoutlen\ttime in msec for 'ttimeout'") +call <SID>AddOption("ttimeoutlen", gettext("time in msec for 'ttimeout'")) call append("$", " \tset ttm=" . &ttm) -call <SID>Header("reading and writing files") -call append("$", "modeline\tenable using settings from modelines when reading a file") -call append("$", "\t(local to buffer)") +call <SID>Header(gettext("reading and writing files")) +call <SID>AddOption("modeline", gettext("enable using settings from modelines when reading a file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ml") -call append("$", "modelineexpr\tallow setting expression options from a modeline") +call <SID>AddOption("modelineexpr", gettext("allow setting expression options from a modeline")) call <SID>BinOptionG("mle", &mle) -call append("$", "modelines\tnumber of lines to check for modelines") +call <SID>AddOption("modelines", gettext("number of lines to check for modelines")) call append("$", " \tset mls=" . &mls) -call append("$", "binary\tbinary file editing") -call append("$", "\t(local to buffer)") +call <SID>AddOption("binary", gettext("binary file editing")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bin") -call append("$", "endofline\tlast line in the file has an end-of-line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("endofline", gettext("last line in the file has an end-of-line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("eol") -call append("$", "fixendofline\tfixes missing end-of-line at end of text file") -call append("$", "\t(local to buffer)") +call <SID>AddOption("endoffile", gettext("last line in the file followed by CTRL-Z")) +call append("$", "\t" .. s:local_to_buffer) +call <SID>BinOptionL("eof") +call <SID>AddOption("fixendofline", gettext("fixes missing end-of-line at end of text file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("fixeol") -call append("$", "bomb\tprepend a Byte Order Mark to the file") -call append("$", "\t(local to buffer)") +call <SID>AddOption("bomb", gettext("prepend a Byte Order Mark to the file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bomb") -call append("$", "fileformat\tend-of-line format: \"dos\", \"unix\" or \"mac\"") -call append("$", "\t(local to buffer)") +call <SID>AddOption("fileformat", gettext("end-of-line format: \"dos\", \"unix\" or \"mac\"")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ff") -call append("$", "fileformats\tlist of file formats to look for when editing a file") +call <SID>AddOption("fileformats", gettext("list of file formats to look for when editing a file")) call <SID>OptionG("ffs", &ffs) -call append("$", "\t(local to buffer)") -call append("$", "write\twriting files is allowed") +call <SID>AddOption("write", gettext("writing files is allowed")) call <SID>BinOptionG("write", &write) -call append("$", "writebackup\twrite a backup file before overwriting a file") +call <SID>AddOption("writebackup", gettext("write a backup file before overwriting a file")) call <SID>BinOptionG("wb", &wb) -call append("$", "backup\tkeep a backup after overwriting a file") +call <SID>AddOption("backup", gettext("keep a backup after overwriting a file")) call <SID>BinOptionG("bk", &bk) -call append("$", "backupskip\tpatterns that specify for which files a backup is not made") +call <SID>AddOption("backupskip", gettext("patterns that specify for which files a backup is not made")) call append("$", " \tset bsk=" . &bsk) -call append("$", "backupcopy\twhether to make the backup as a copy or rename the existing file") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("backupcopy", gettext("whether to make the backup as a copy or rename the existing file")) +call append("$", "\t" .. s:global_or_local) call append("$", " \tset bkc=" . &bkc) -call append("$", "backupdir\tlist of directories to put backup files in") +call <SID>AddOption("backupdir", gettext("list of directories to put backup files in")) call <SID>OptionG("bdir", &bdir) -call append("$", "backupext\tfile name extension for the backup file") +call <SID>AddOption("backupext", gettext("file name extension for the backup file")) call <SID>OptionG("bex", &bex) -call append("$", "autowrite\tautomatically write a file when leaving a modified buffer") +call <SID>AddOption("autowrite", gettext("automatically write a file when leaving a modified buffer")) call <SID>BinOptionG("aw", &aw) -call append("$", "autowriteall\tas 'autowrite', but works with more commands") +call <SID>AddOption("autowriteall", gettext("as 'autowrite', but works with more commands")) call <SID>BinOptionG("awa", &awa) -call append("$", "writeany\talways write without asking for confirmation") +call <SID>AddOption("writeany", gettext("always write without asking for confirmation")) call <SID>BinOptionG("wa", &wa) -call append("$", "autoread\tautomatically read a file when it was modified outside of Vim") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("autoread", gettext("automatically read a file when it was modified outside of Vim")) +call append("$", "\t" .. s:global_or_local) call <SID>BinOptionG("ar", &ar) -call append("$", "patchmode\tkeep oldest version of a file; specifies file name extension") +call <SID>AddOption("patchmode", gettext("keep oldest version of a file; specifies file name extension")) call <SID>OptionG("pm", &pm) -call append("$", "fsync\tforcibly sync the file to disk after writing it") +call <SID>AddOption("fsync", gettext("forcibly sync the file to disk after writing it")) call <SID>BinOptionG("fs", &fs) -call <SID>Header("the swap file") -call append("$", "directory\tlist of directories for the swap file") +call <SID>Header(gettext("the swap file")) +call <SID>AddOption("directory", gettext("list of directories for the swap file")) call <SID>OptionG("dir", &dir) -call append("$", "swapfile\tuse a swap file for this buffer") -call append("$", "\t(local to buffer)") +call <SID>AddOption("swapfile", gettext("use a swap file for this buffer")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("swf") -call append("$", "updatecount\tnumber of characters typed to cause a swap file update") +call <SID>AddOption("updatecount", gettext("number of characters typed to cause a swap file update")) call append("$", " \tset uc=" . &uc) -call append("$", "updatetime\ttime in msec after which the swap file will be updated") +call <SID>AddOption("updatetime", gettext("time in msec after which the swap file will be updated")) call append("$", " \tset ut=" . &ut) -call <SID>Header("command line editing") -call <SID>AddOption("history", "how many command lines are remembered") +call <SID>Header(gettext("command line editing")) +call <SID>AddOption("history", gettext("how many command lines are remembered")) call append("$", " \tset hi=" . &hi) -call append("$", "wildchar\tkey that triggers command-line expansion") +call <SID>AddOption("wildchar", gettext("key that triggers command-line expansion")) call append("$", " \tset wc=" . &wc) -call append("$", "wildcharm\tlike 'wildchar' but can also be used in a mapping") +call <SID>AddOption("wildcharm", gettext("like 'wildchar' but can also be used in a mapping")) call append("$", " \tset wcm=" . &wcm) -call append("$", "wildmode\tspecifies how command line completion works") +call <SID>AddOption("wildmode", gettext("specifies how command line completion works")) call <SID>OptionG("wim", &wim) if has("wildoptions") - call append("$", "wildoptions\tempty or \"tagfile\" to list file name of matching tags") + call <SID>AddOption("wildoptions", gettext("empty or \"tagfile\" to list file name of matching tags")) call <SID>OptionG("wop", &wop) endif -call append("$", "suffixes\tlist of file name extensions that have a lower priority") +call <SID>AddOption("suffixes", gettext("list of file name extensions that have a lower priority")) call <SID>OptionG("su", &su) if has("file_in_path") - call append("$", "suffixesadd\tlist of file name extensions added when searching for a file") - call append("$", "\t(local to buffer)") + call <SID>AddOption("suffixesadd", gettext("list of file name extensions added when searching for a file")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("sua") endif if has("wildignore") - call append("$", "wildignore\tlist of patterns to ignore files for file name completion") + call <SID>AddOption("wildignore", gettext("list of patterns to ignore files for file name completion")) call <SID>OptionG("wig", &wig) endif -call append("$", "fileignorecase\tignore case when using file names") +call <SID>AddOption("fileignorecase", gettext("ignore case when using file names")) call <SID>BinOptionG("fic", &fic) -call append("$", "wildignorecase\tignore case when completing file names") +call <SID>AddOption("wildignorecase", gettext("ignore case when completing file names")) call <SID>BinOptionG("wic", &wic) if has("wildmenu") - call append("$", "wildmenu\tcommand-line completion shows a list of matches") + call <SID>AddOption("wildmenu", gettext("command-line completion shows a list of matches")) call <SID>BinOptionG("wmnu", &wmnu) endif -call append("$", "cedit\tkey used to open the command-line window") +call <SID>AddOption("cedit", gettext("key used to open the command-line window")) call <SID>OptionG("cedit", &cedit) -call append("$", "cmdwinheight\theight of the command-line window") +call <SID>AddOption("cmdwinheight", gettext("height of the command-line window")) call <SID>OptionG("cwh", &cwh) -call <SID>Header("executing external commands") -call append("$", "shell\tname of the shell program used for external commands") +call <SID>Header(gettext("executing external commands")) +call <SID>AddOption("shell", gettext("name of the shell program used for external commands")) call <SID>OptionG("sh", &sh) -call append("$", "shellquote\tcharacter(s) to enclose a shell command in") +call <SID>AddOption("shellquote", gettext("character(s) to enclose a shell command in")) call <SID>OptionG("shq", &shq) -call append("$", "shellxquote\tlike 'shellquote' but include the redirection") +call <SID>AddOption("shellxquote", gettext("like 'shellquote' but include the redirection")) call <SID>OptionG("sxq", &sxq) -call append("$", "shellxescape\tcharacters to escape when 'shellxquote' is (") +call <SID>AddOption("shellxescape", gettext("characters to escape when 'shellxquote' is (")) call <SID>OptionG("sxe", &sxe) -call append("$", "shellcmdflag\targument for 'shell' to execute a command") +call <SID>AddOption("shellcmdflag", gettext("argument for 'shell' to execute a command")) call <SID>OptionG("shcf", &shcf) -call append("$", "shellredir\tused to redirect command output to a file") +call <SID>AddOption("shellredir", gettext("used to redirect command output to a file")) call <SID>OptionG("srr", &srr) -call append("$", "shelltemp\tuse a temp file for shell commands instead of using a pipe") +call <SID>AddOption("shelltemp", gettext("use a temp file for shell commands instead of using a pipe")) call <SID>BinOptionG("stmp", &stmp) -call append("$", "equalprg\tprogram used for \"=\" command") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("equalprg", gettext("program used for \"=\" command")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("ep", &ep) -call append("$", "formatprg\tprogram used to format lines with \"gq\" command") +call <SID>AddOption("formatprg", gettext("program used to format lines with \"gq\" command")) call <SID>OptionG("fp", &fp) -call append("$", "keywordprg\tprogram used for the \"K\" command") +call <SID>AddOption("keywordprg", gettext("program used for the \"K\" command")) call <SID>OptionG("kp", &kp) -call append("$", "warn\twarn when using a shell command and a buffer has changes") +call <SID>AddOption("warn", gettext("warn when using a shell command and a buffer has changes")) call <SID>BinOptionG("warn", &warn) if has("quickfix") - call <SID>Header("running make and jumping to errors (quickfix)") - call append("$", "errorfile\tname of the file that contains error messages") + call <SID>Header(gettext("running make and jumping to errors (quickfix)")) + call <SID>AddOption("errorfile", gettext("name of the file that contains error messages")) call <SID>OptionG("ef", &ef) - call append("$", "errorformat\tlist of formats for error messages") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("errorformat", gettext("list of formats for error messages")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("efm", &efm) - call append("$", "makeprg\tprogram used for the \":make\" command") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("makeprg", gettext("program used for the \":make\" command")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("mp", &mp) - call append("$", "shellpipe\tstring used to put the output of \":make\" in the error file") + call <SID>AddOption("shellpipe", gettext("string used to put the output of \":make\" in the error file")) call <SID>OptionG("sp", &sp) - call append("$", "makeef\tname of the errorfile for the 'makeprg' command") + call <SID>AddOption("makeef", gettext("name of the errorfile for the 'makeprg' command")) call <SID>OptionG("mef", &mef) - call append("$", "grepprg\tprogram used for the \":grep\" command") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("grepprg", gettext("program used for the \":grep\" command")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("gp", &gp) - call append("$", "grepformat\tlist of formats for output of 'grepprg'") + call <SID>AddOption("grepformat", gettext("list of formats for output of 'grepprg'")) call <SID>OptionG("gfm", &gfm) - call append("$", "makeencoding\tencoding of the \":make\" and \":grep\" output") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("makeencoding", gettext("encoding of the \":make\" and \":grep\" output")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("menc", &menc) endif if has("win32") - call <SID>Header("system specific") - call <SID>AddOption("shellslash", "use forward slashes in file names; for Unix-like shells") + call <SID>Header(gettext("system specific")) + call <SID>AddOption("shellslash", gettext("use forward slashes in file names; for Unix-like shells")) call <SID>BinOptionG("ssl", &ssl) - call <SID>AddOption("completeslash", "specifies slash/backslash used for completion") + call <SID>AddOption("completeslash", gettext("specifies slash/backslash used for completion")) call <SID>OptionG("csl", &csl) endif -call <SID>Header("language specific") -call append("$", "isfname\tspecifies the characters in a file name") +call <SID>Header(gettext("language specific")) +call <SID>AddOption("isfname", gettext("specifies the characters in a file name")) call <SID>OptionG("isf", &isf) -call append("$", "isident\tspecifies the characters in an identifier") +call <SID>AddOption("isident", gettext("specifies the characters in an identifier")) call <SID>OptionG("isi", &isi) -call append("$", "iskeyword\tspecifies the characters in a keyword") -call append("$", "\t(local to buffer)") +call <SID>AddOption("iskeyword", gettext("specifies the characters in a keyword")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("isk") -call append("$", "isprint\tspecifies printable characters") +call <SID>AddOption("isprint", gettext("specifies printable characters")) call <SID>OptionG("isp", &isp) if has("textobjects") - call append("$", "quoteescape\tspecifies escape characters in a string") - call append("$", "\t(local to buffer)") + call <SID>AddOption("quoteescape", gettext("specifies escape characters in a string")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("qe") endif if has("rightleft") - call append("$", "rightleft\tdisplay the buffer right-to-left") - call append("$", "\t(local to window)") + call <SID>AddOption("rightleft", gettext("display the buffer right-to-left")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("rl") - call append("$", "rightleftcmd\twhen to edit the command-line right-to-left") - call append("$", "\t(local to window)") + call <SID>AddOption("rightleftcmd", gettext("when to edit the command-line right-to-left")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("rlc") - call append("$", "revins\tinsert characters backwards") + call <SID>AddOption("revins", gettext("insert characters backwards")) call <SID>BinOptionG("ri", &ri) - call append("$", "allowrevins\tallow CTRL-_ in Insert and Command-line mode to toggle 'revins'") + call <SID>AddOption("allowrevins", gettext("allow CTRL-_ in Insert and Command-line mode to toggle 'revins'")) call <SID>BinOptionG("ari", &ari) - call append("$", "aleph\tthe ASCII code for the first letter of the Hebrew alphabet") + call <SID>AddOption("aleph", gettext("the ASCII code for the first letter of the Hebrew alphabet")) call append("$", " \tset al=" . &al) - call append("$", "hkmap\tuse Hebrew keyboard mapping") + call <SID>AddOption("hkmap", gettext("use Hebrew keyboard mapping")) call <SID>BinOptionG("hk", &hk) - call append("$", "hkmapp\tuse phonetic Hebrew keyboard mapping") + call <SID>AddOption("hkmapp", gettext("use phonetic Hebrew keyboard mapping")) call <SID>BinOptionG("hkp", &hkp) endif if has("arabic") - call append("$", "arabic\tprepare for editing Arabic text") - call append("$", "\t(local to window)") + call <SID>AddOption("arabic", gettext("prepare for editing Arabic text")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("arab") - call append("$", "arabicshape\tperform shaping of Arabic characters") + call <SID>AddOption("arabicshape", gettext("perform shaping of Arabic characters")) call <SID>BinOptionG("arshape", &arshape) - call append("$", "termbidi\tterminal will perform bidi handling") + call <SID>AddOption("termbidi", gettext("terminal will perform bidi handling")) call <SID>BinOptionG("tbidi", &tbidi) endif if has("keymap") - call append("$", "keymap\tname of a keyboard mapping") + call <SID>AddOption("keymap", gettext("name of a keyboard mapping")) call <SID>OptionL("kmp") endif if has("langmap") - call append("$", "langmap\tlist of characters that are translated in Normal mode") + call <SID>AddOption("langmap", gettext("list of characters that are translated in Normal mode")) call <SID>OptionG("lmap", &lmap) - call append("$", "langremap\tapply 'langmap' to mapped characters") + call <SID>AddOption("langremap", gettext("apply 'langmap' to mapped characters")) call <SID>BinOptionG("lrm", &lrm) endif if has("xim") - call append("$", "imdisable\twhen set never use IM; overrules following IM options") + call <SID>AddOption("imdisable", gettext("when set never use IM; overrules following IM options")) call <SID>BinOptionG("imd", &imd) endif -call append("$", "iminsert\tin Insert mode: 1: use :lmap; 2: use IM; 0: neither") -call append("$", "\t(local to window)") +call <SID>AddOption("iminsert", gettext("in Insert mode: 1: use :lmap; 2: use IM; 0: neither")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("imi") -call append("$", "imsearch\tentering a search pattern: 1: use :lmap; 2: use IM; 0: neither") -call append("$", "\t(local to window)") +call <SID>AddOption("imsearch", gettext("entering a search pattern: 1: use :lmap; 2: use IM; 0: neither")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("ims") if has("xim") - call append("$", "imcmdline\twhen set always use IM when starting to edit a command line") + call <SID>AddOption("imcmdline", gettext("when set always use IM when starting to edit a command line")) call <SID>BinOptionG("imc", &imc) - call append("$", "imstatusfunc\tfunction to obtain IME status") + call <SID>AddOption("imstatusfunc", gettext("function to obtain IME status")) call <SID>OptionG("imsf", &imsf) - call append("$", "imactivatefunc\tfunction to enable/disable IME") + call <SID>AddOption("imactivatefunc", gettext("function to enable/disable IME")) call <SID>OptionG("imaf", &imaf) endif -call <SID>Header("multi-byte characters") -call <SID>AddOption("encoding", "character encoding used in Nvim: \"utf-8\"") +call <SID>Header(gettext("multi-byte characters")) +call <SID>AddOption("encoding", gettext("character encoding used in Nvim: \"utf-8\"")) call <SID>OptionG("enc", &enc) -call append("$", "fileencoding\tcharacter encoding for the current file") +call <SID>AddOption("fileencoding", gettext("character encoding for the current file")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fenc") -call append("$", "fileencodings\tautomatically detected character encodings") +call <SID>AddOption("fileencodings", gettext("automatically detected character encodings")) call <SID>OptionG("fencs", &fencs) -call append("$", "charconvert\texpression used for character encoding conversion") +call <SID>AddOption("charconvert", gettext("expression used for character encoding conversion")) call <SID>OptionG("ccv", &ccv) -call append("$", "delcombine\tdelete combining (composing) characters on their own") +call <SID>AddOption("delcombine", gettext("delete combining (composing) characters on their own")) call <SID>BinOptionG("deco", &deco) -call append("$", "maxcombine\tmaximum number of combining (composing) characters displayed") +call <SID>AddOption("maxcombine", gettext("maximum number of combining (composing) characters displayed")) call <SID>OptionG("mco", &mco) if has("xim") && has("gui_gtk") - call append("$", "imactivatekey\tkey that activates the X input method") + call <SID>AddOption("imactivatekey", gettext("key that activates the X input method")) call <SID>OptionG("imak", &imak) endif -call append("$", "ambiwidth\twidth of ambiguous width characters") +call <SID>AddOption("ambiwidth", gettext("width of ambiguous width characters")) call <SID>OptionG("ambw", &ambw) -call append("$", "emoji\temoji characters are full width") +call <SID>AddOption("emoji", gettext("emoji characters are full width")) call <SID>BinOptionG("emo", &emo) -call <SID>Header("various") -call <SID>AddOption("virtualedit", "when to use virtual editing: \"block\", \"insert\", \"all\"\nand/or \"onemore\"") +call <SID>Header(gettext("various")) +call <SID>AddOption("virtualedit", gettext("when to use virtual editing: \"block\", \"insert\", \"all\"\nand/or \"onemore\"")) call <SID>OptionG("ve", &ve) -call append("$", "eventignore\tlist of autocommand events which are to be ignored") +call <SID>AddOption("eventignore", gettext("list of autocommand events which are to be ignored")) call <SID>OptionG("ei", &ei) -call append("$", "loadplugins\tload plugin scripts when starting up") +call <SID>AddOption("loadplugins", gettext("load plugin scripts when starting up")) call <SID>BinOptionG("lpl", &lpl) -call append("$", "exrc\tenable reading .vimrc/.exrc/.gvimrc in the current directory") +call <SID>AddOption("exrc", gettext("enable reading .vimrc/.exrc/.gvimrc in the current directory")) call <SID>BinOptionG("ex", &ex) -call append("$", "secure\tsafer working with script files in the current directory") +call <SID>AddOption("secure", gettext("safer working with script files in the current directory")) call <SID>BinOptionG("secure", &secure) -call append("$", "gdefault\tuse the 'g' flag for \":substitute\"") +call <SID>AddOption("gdefault", gettext("use the 'g' flag for \":substitute\"")) call <SID>BinOptionG("gd", &gd) if exists("+opendevice") - call append("$", "opendevice\tallow reading/writing devices") + call <SID>AddOption("opendevice", gettext("allow reading/writing devices")) call <SID>BinOptionG("odev", &odev) endif if exists("+maxfuncdepth") - call append("$", "maxfuncdepth\tmaximum depth of function calls") + call <SID>AddOption("maxfuncdepth", gettext("maximum depth of function calls")) call append("$", " \tset mfd=" . &mfd) endif if has("mksession") - call append("$", "sessionoptions\tlist of words that specifies what to put in a session file") + call <SID>AddOption("sessionoptions", gettext("list of words that specifies what to put in a session file")) call <SID>OptionG("ssop", &ssop) - call append("$", "viewoptions\tlist of words that specifies what to save for :mkview") + call <SID>AddOption("viewoptions", gettext("list of words that specifies what to save for :mkview")) call <SID>OptionG("vop", &vop) - call append("$", "viewdir\tdirectory where to store files with :mkview") + call <SID>AddOption("viewdir", gettext("directory where to store files with :mkview")) call <SID>OptionG("vdir", &vdir) endif if has("shada") - call append("$", "viminfo\tlist that specifies what to write in the ShaDa file") + call <SID>AddOption("viminfo", gettext("list that specifies what to write in the ShaDa file")) call <SID>OptionG("vi", &vi) endif if has("quickfix") - call append("$", "bufhidden\twhat happens with a buffer when it's no longer in a window") + call <SID>AddOption("bufhidden", gettext("what happens with a buffer when it's no longer in a window")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("bh") - call <SID>AddOption("buftype", "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer") + call <SID>AddOption("buftype", gettext("empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("bt") endif -call append("$", "buflisted\twhether the buffer shows up in the buffer list") -call append("$", "\t(local to buffer)") +call <SID>AddOption("buflisted", gettext("whether the buffer shows up in the buffer list")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bl") -call append("$", "debug\tset to \"msg\" to see all error messages") +call <SID>AddOption("debug", gettext("set to \"msg\" to see all error messages")) call append("$", " \tset debug=" . &debug) -call append("$", "signcolumn\twhether to show the signcolumn") -call append("$", "\t(local to window)") +call <SID>AddOption("signcolumn", gettext("whether to show the signcolumn")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scl") set cpo&vim @@ -1285,13 +1279,13 @@ if has("syntax") endif endif if exists("&mzschemedll") - call append("$", "mzschemedll\tname of the Tcl dynamic library") + call <SID>AddOption("mzschemedll", gettext("name of the MzScheme dynamic library")) call <SID>OptionG("mzschemedll", &mzschemedll) - call append("$", "mzschemegcdll\tname of the Tcl GC dynamic library") + call <SID>AddOption("mzschemegcdll", gettext("name of the MzScheme GC dynamic library")) call <SID>OptionG("mzschemegcdll", &mzschemegcdll) endif if has('pythonx') - call append("$", "pyxversion\twhether to use Python 2 or 3") + call <SID>AddOption("pyxversion", gettext("whether to use Python 2 or 3")) call append("$", " \tset pyx=" . &wd) endif @@ -1308,7 +1302,7 @@ augroup optwin \ call <SID>unload() | delfun <SID>unload augroup END -fun! <SID>unload() +func <SID>unload() delfun <SID>CR delfun <SID>Space delfun <SID>Find @@ -1319,7 +1313,7 @@ fun! <SID>unload() delfun <SID>BinOptionG delfun <SID>Header au! optwin -endfun +endfunc " Restore the previous value of 'title' and 'icon'. let &title = s:old_title diff --git a/runtime/queries/help/highlights.scm b/runtime/queries/help/highlights.scm index cd4b6f8269..b2ed390336 100644 --- a/runtime/queries/help/highlights.scm +++ b/runtime/queries/help/highlights.scm @@ -2,6 +2,8 @@ (h2) @text.title (h3) @text.title (column_heading) @text.title +(column_heading + "~" @conceal (#set! conceal "")) (tag "*" @conceal (#set! conceal "") text: (_) @label) @@ -14,6 +16,10 @@ "`" @conceal (#set! conceal "") text: (_) @text.literal) (codeblock) @text.literal +(codeblock + ">" @conceal (#set! conceal "")) +(block + "<" @conceal (#set! conceal "")) (argument) @parameter (keycode) @string.special (url) @text.uri diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 054d787932..2c0dc5447a 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -131,6 +131,11 @@ ((identifier) @variable.builtin (#eq? @variable.builtin "self")) +(variable_list + attribute: (attribute + (["<" ">"] @punctuation.bracket + (identifier) @attribute))) + ;; Constants ((identifier) @constant diff --git a/runtime/syntax/2html.vim b/runtime/syntax/2html.vim index 8adbd76950..76f3ef7589 100644 --- a/runtime/syntax/2html.vim +++ b/runtime/syntax/2html.vim @@ -815,202 +815,204 @@ endif " HTML header, with the title and generator ;-). Left free space for the CSS, " to be filled at the end. -call extend(s:lines, [ - \ "<html>", - \ "<head>"]) -" include encoding as close to the top as possible, but only if not already -" contained in XML information (to avoid haggling over content type) -if s:settings.encoding != "" && !s:settings.use_xhtml - if s:html5 - call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close) - else - call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close) - endif -endif -call extend(s:lines, [ - \ ("<title>".expand("%:p:~")."</title>"), - \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close), - \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close) - \ ]) -call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close) -call add(s:lines, '<meta name="settings" content="'. - \ join(filter(keys(s:settings),'s:settings[v:val]'),','). - \ ',prevent_copy='.s:settings.prevent_copy. - \ ',use_input_for_pc='.s:settings.use_input_for_pc. - \ '"'.s:tag_close) -call add(s:lines, '<meta name="colorscheme" content="'. - \ (exists('g:colors_name') - \ ? g:colors_name - \ : 'none'). '"'.s:tag_close) - -if s:settings.use_css +if !s:settings.no_doc call extend(s:lines, [ - \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">", - \ s:settings.use_xhtml ? "" : "<!--"]) - let s:ieonly = [] - if s:settings.dynamic_folds - if s:settings.hover_unfold - " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6 - call extend(s:lines, [ - \ ".FoldColumn { text-decoration: none; white-space: pre; }", - \ "", - \ "body * { margin: 0; padding: 0; }", "", - \ ".open-fold > span.Folded { display: none; }", - \ ".open-fold > .fulltext { display: inline; }", - \ ".closed-fold > .fulltext { display: none; }", - \ ".closed-fold > span.Folded { display: inline; }", - \ "", - \ ".open-fold > .toggle-open { display: none; }", - \ ".open-fold > .toggle-closed { display: inline; }", - \ ".closed-fold > .toggle-open { display: inline; }", - \ ".closed-fold > .toggle-closed { display: none; }", - \ "", "", - \ '/* opening a fold while hovering won''t be supported by IE6 and other', - \ "similar browsers, but it should fail gracefully. */", - \ ".closed-fold:hover > .fulltext { display: inline; }", - \ ".closed-fold:hover > .toggle-filler { display: none; }", - \ ".closed-fold:hover > .Folded { display: none; }"]) - " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we - " should remove this? Leave it in for now, it was working at one point, - " and doesn't affect any modern browsers. Even newer IE versions should - " support the above code and ignore the following. - let s:ieonly = [ - \ "<!--[if lt IE 7]><style type=\"text/css\">", - \ ".open-fold .fulltext { display: inline; }", - \ ".open-fold span.Folded { display: none; }", - \ ".open-fold .toggle-open { display: none; }", - \ ".open-fold .toggle-closed { display: inline; }", - \ "", - \ ".closed-fold .fulltext { display: none; }", - \ ".closed-fold span.Folded { display: inline; }", - \ ".closed-fold .toggle-open { display: inline; }", - \ ".closed-fold .toggle-closed { display: none; }", - \ "</style>", - \ "<![endif]-->", - \] + \ "<html>", + \ "<head>"]) + " include encoding as close to the top as possible, but only if not already + " contained in XML information (to avoid haggling over content type) + if s:settings.encoding != "" && !s:settings.use_xhtml + if s:html5 + call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close) else - " if we aren't doing hover_unfold, use CSS 1 only - call extend(s:lines, [ - \ ".FoldColumn { text-decoration: none; white-space: pre; }", - \ ".open-fold .fulltext { display: inline; }", - \ ".open-fold span.Folded { display: none; }", - \ ".open-fold .toggle-open { display: none; }", - \ ".open-fold .toggle-closed { display: inline; }", - \ "", - \ ".closed-fold .fulltext { display: none; }", - \ ".closed-fold span.Folded { display: inline; }", - \ ".closed-fold .toggle-open { display: inline; }", - \ ".closed-fold .toggle-closed { display: none; }", - \]) + call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close) endif endif - " else we aren't doing any dynamic folding, no need for any special rules - call extend(s:lines, [ - \ s:settings.use_xhtml ? "" : '-->', - \ "</style>", - \]) - call extend(s:lines, s:ieonly) - unlet s:ieonly -endif + \ ("<title>".expand("%:p:~")."</title>"), + \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close), + \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close) + \ ]) + call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close) + call add(s:lines, '<meta name="settings" content="'. + \ join(filter(keys(s:settings),'s:settings[v:val]'),','). + \ ',prevent_copy='.s:settings.prevent_copy. + \ ',use_input_for_pc='.s:settings.use_input_for_pc. + \ '"'.s:tag_close) + call add(s:lines, '<meta name="colorscheme" content="'. + \ (exists('g:colors_name') + \ ? g:colors_name + \ : 'none'). '"'.s:tag_close) -let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids + if s:settings.use_css + call extend(s:lines, [ + \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">", + \ s:settings.use_xhtml ? "" : "<!--"]) + let s:ieonly = [] + if s:settings.dynamic_folds + if s:settings.hover_unfold + " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6 + call extend(s:lines, [ + \ ".FoldColumn { text-decoration: none; white-space: pre; }", + \ "", + \ "body * { margin: 0; padding: 0; }", "", + \ ".open-fold > span.Folded { display: none; }", + \ ".open-fold > .fulltext { display: inline; }", + \ ".closed-fold > .fulltext { display: none; }", + \ ".closed-fold > span.Folded { display: inline; }", + \ "", + \ ".open-fold > .toggle-open { display: none; }", + \ ".open-fold > .toggle-closed { display: inline; }", + \ ".closed-fold > .toggle-open { display: inline; }", + \ ".closed-fold > .toggle-closed { display: none; }", + \ "", "", + \ '/* opening a fold while hovering won''t be supported by IE6 and other', + \ "similar browsers, but it should fail gracefully. */", + \ ".closed-fold:hover > .fulltext { display: inline; }", + \ ".closed-fold:hover > .toggle-filler { display: none; }", + \ ".closed-fold:hover > .Folded { display: none; }"]) + " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we + " should remove this? Leave it in for now, it was working at one point, + " and doesn't affect any modern browsers. Even newer IE versions should + " support the above code and ignore the following. + let s:ieonly = [ + \ "<!--[if lt IE 7]><style type=\"text/css\">", + \ ".open-fold .fulltext { display: inline; }", + \ ".open-fold span.Folded { display: none; }", + \ ".open-fold .toggle-open { display: none; }", + \ ".open-fold .toggle-closed { display: inline; }", + \ "", + \ ".closed-fold .fulltext { display: none; }", + \ ".closed-fold span.Folded { display: inline; }", + \ ".closed-fold .toggle-open { display: inline; }", + \ ".closed-fold .toggle-closed { display: none; }", + \ "</style>", + \ "<![endif]-->", + \] + else + " if we aren't doing hover_unfold, use CSS 1 only + call extend(s:lines, [ + \ ".FoldColumn { text-decoration: none; white-space: pre; }", + \ ".open-fold .fulltext { display: inline; }", + \ ".open-fold span.Folded { display: none; }", + \ ".open-fold .toggle-open { display: none; }", + \ ".open-fold .toggle-closed { display: inline; }", + \ "", + \ ".closed-fold .fulltext { display: none; }", + \ ".closed-fold span.Folded { display: inline; }", + \ ".closed-fold .toggle-open { display: inline; }", + \ ".closed-fold .toggle-closed { display: none; }", + \]) + endif + endif + " else we aren't doing any dynamic folding, no need for any special rules -" insert script tag if needed -if s:uses_script - call extend(s:lines, [ - \ "", - \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", - \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) -endif + call extend(s:lines, [ + \ s:settings.use_xhtml ? "" : '-->', + \ "</style>", + \]) + call extend(s:lines, s:ieonly) + unlet s:ieonly + endif -" insert javascript to toggle folds open and closed -if s:settings.dynamic_folds - call extend(s:lines, [ - \ "", - \ "function toggleFold(objID)", - \ "{", - \ " var fold;", - \ " fold = document.getElementById(objID);", - \ " if (fold.className == 'closed-fold')", - \ " {", - \ " fold.className = 'open-fold';", - \ " }", - \ " else if (fold.className == 'open-fold')", - \ " {", - \ " fold.className = 'closed-fold';", - \ " }", - \ "}" - \ ]) -endif + let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids -if s:settings.line_ids - " insert javascript to get IDs from line numbers, and to open a fold before - " jumping to any lines contained therein - call extend(s:lines, [ - \ "", - \ "/* function to open any folds containing a jumped-to line before jumping to it */", - \ "function JumpToLine()", - \ "{", - \ " var lineNum;", - \ " lineNum = window.location.hash;", - \ " lineNum = lineNum.substr(1); /* strip off '#' */", - \ "", - \ " if (lineNum.indexOf('L') == -1) {", - \ " lineNum = 'L'+lineNum;", - \ " }", - \ " var lineElem = document.getElementById(lineNum);" - \ ]) + " insert script tag if needed + if s:uses_script + call extend(s:lines, [ + \ "", + \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", + \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) + endif + " insert javascript to toggle folds open and closed if s:settings.dynamic_folds call extend(s:lines, [ \ "", - \ " /* navigate upwards in the DOM tree to open all folds containing the line */", - \ " var node = lineElem;", - \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ "function toggleFold(objID)", + \ "{", + \ " var fold;", + \ " fold = document.getElementById(objID);", + \ " if (fold.className == 'closed-fold')", + \ " {", + \ " fold.className = 'open-fold';", + \ " }", + \ " else if (fold.className == 'open-fold')", \ " {", - \ " if (node.className == 'closed-fold')", - \ " {", - \ " node.className = 'open-fold';", - \ " }", - \ " node = node.parentNode;", + \ " fold.className = 'closed-fold';", \ " }", + \ "}" \ ]) endif - call extend(s:lines, [ - \ " /* Always jump to new location even if the line was hidden inside a fold, or", - \ " * we corrected the raw number to a line ID.", - \ " */", - \ " if (lineElem) {", - \ " lineElem.scrollIntoView(true);", - \ " }", - \ " return true;", - \ "}", - \ "if ('onhashchange' in window) {", - \ " window.onhashchange = JumpToLine;", - \ "}" - \ ]) -endif -" insert script closing tag if needed -if s:uses_script - call extend(s:lines, [ - \ '', - \ s:settings.use_xhtml ? '//]]>' : '-->', - \ "</script>" - \ ]) -endif + if s:settings.line_ids + " insert javascript to get IDs from line numbers, and to open a fold before + " jumping to any lines contained therein + call extend(s:lines, [ + \ "", + \ "/* function to open any folds containing a jumped-to line before jumping to it */", + \ "function JumpToLine()", + \ "{", + \ " var lineNum;", + \ " lineNum = window.location.hash;", + \ " lineNum = lineNum.substr(1); /* strip off '#' */", + \ "", + \ " if (lineNum.indexOf('L') == -1) {", + \ " lineNum = 'L'+lineNum;", + \ " }", + \ " var lineElem = document.getElementById(lineNum);" + \ ]) + + if s:settings.dynamic_folds + call extend(s:lines, [ + \ "", + \ " /* navigate upwards in the DOM tree to open all folds containing the line */", + \ " var node = lineElem;", + \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ " {", + \ " if (node.className == 'closed-fold')", + \ " {", + \ " node.className = 'open-fold';", + \ " }", + \ " node = node.parentNode;", + \ " }", + \ ]) + endif + call extend(s:lines, [ + \ " /* Always jump to new location even if the line was hidden inside a fold, or", + \ " * we corrected the raw number to a line ID.", + \ " */", + \ " if (lineElem) {", + \ " lineElem.scrollIntoView(true);", + \ " }", + \ " return true;", + \ "}", + \ "if ('onhashchange' in window) {", + \ " window.onhashchange = JumpToLine;", + \ "}" + \ ]) + endif -call extend(s:lines, ["</head>", - \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"]) + " insert script closing tag if needed + if s:uses_script + call extend(s:lines, [ + \ '', + \ s:settings.use_xhtml ? '//]]>' : '-->', + \ "</script>" + \ ]) + endif + + call extend(s:lines, ["</head>", + \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"]) +endif if s:settings.no_pre " if we're not using CSS we use a font tag which can't have a div inside if s:settings.use_css - call extend(s:lines, ["<div id='vimCodeElement".s:settings.id_suffix."'>"]) + call extend(s:lines, ["<div id='vimCodeElement" .. s:settings.id_suffix .. "'>"]) endif else - call extend(s:lines, ["<pre id='vimCodeElement".s:settings.id_suffix."'>"]) + call extend(s:lines, ["<pre id='vimCodeElement" .. s:settings.id_suffix .. "'>"]) endif exe s:orgwin . "wincmd w" @@ -1721,12 +1723,15 @@ endif if s:settings.no_pre if !s:settings.use_css " Close off the font tag that encapsulates the whole <body> - call extend(s:lines, ["</font>", "</body>", "</html>"]) + call extend(s:lines, ["</font>"]) else - call extend(s:lines, ["</div>", "</body>", "</html>"]) + call extend(s:lines, ["</div>"]) endif else - call extend(s:lines, ["</pre>", "</body>", "</html>"]) + call extend(s:lines, ["</pre>"]) +endif +if !s:settings.no_doc + call extend(s:lines, ["</body>", "</html>"]) endif exe s:newwin . "wincmd w" @@ -1742,15 +1747,15 @@ unlet s:lines " The generated HTML is admittedly ugly and takes a LONG time to fold. " Make sure the user doesn't do syntax folding when loading a generated file, " using a modeline. -call append(line('$'), "<!-- vim: set foldmethod=manual : -->") +if !s:settings.no_modeline + call append(line('$'), "<!-- vim: set foldmethod=manual : -->") +endif " Now, when we finally know which, we define the colors and styles -if s:settings.use_css +if s:settings.use_css && !s:settings.no_doc 1;/<style\>/+1 -endif -" Normal/global attributes -if s:settings.use_css + " Normal/global attributes if s:settings.no_pre call append('.', "body { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: ". s:htmlfont ."; }") + @@ -1874,7 +1879,9 @@ if s:settings.use_css endif endif endif -else +endif + +if !s:settings.use_css && !s:settings_no_doc " For Netscape 4, set <body> attributes too, though, strictly speaking, it's " incorrect. execute '%s:<body\([^>]*\):<body bgcolor="' . s:bgc . '" text="' . s:fgc . '"\1>\r<font face="'. s:htmlfont .'"' @@ -1882,7 +1889,7 @@ endif " Gather attributes for all other classes. Do diff first so that normal " highlight groups are inserted before it. -if s:settings.use_css +if s:settings.use_css && !s:settings.no_doc if s:diff_mode call append('.', filter(map(keys(s:diffstylelist), "s:diffstylelist[v:val]"), 'v:val != ""')) endif @@ -1892,20 +1899,22 @@ if s:settings.use_css endif " Add hyperlinks -" TODO: add option to not do this? Maybe just make the color the same as the -" text highlight group normally is? -%s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|>\|<\|"\)+<a href="\1">\1</a>\2+ge +if !s:settings.no_links + %s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|>\|<\|"\)+<a href="\1">\1</a>\2+ge +endif " The DTD -if s:settings.use_xhtml - exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" -elseif s:html5 - exe "normal! gg0i<!DOCTYPE html>\n" -else - exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" +if !s:settings.no_doc + if s:settings.use_xhtml + exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" + elseif s:html5 + exe "normal! gg0i<!DOCTYPE html>\n" + else + exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" + endif endif -if s:settings.use_xhtml +if s:settings.use_xhtml && !s:settings.no_doc exe "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e" endif diff --git a/runtime/syntax/help.vim b/runtime/syntax/help.vim index 5773e94c3e..1882fe8d67 100644 --- a/runtime/syntax/help.vim +++ b/runtime/syntax/help.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Vim help file " Maintainer: Bram Moolenaar (Bram@vim.org) -" Last Change: 2022 Sep 26 +" Last Change: 2022 Oct 17 " Quit when a (custom) syntax file was already loaded if exists("b:current_syntax") @@ -11,7 +11,7 @@ endif let s:cpo_save = &cpo set cpo&vim -syn match helpHeadline "^[-A-Z .][-A-Z0-9 .()_]*\ze\(\s\+\*\|$\)" +syn match helpHeadline "^ *[-A-Z.][-A-Z0-9 .()_]*?\=\ze\(\s\+\*\|$\)" syn match helpSectionDelim "^===.*===$" syn match helpSectionDelim "^---.*--$" if has("conceal") diff --git a/runtime/syntax/openvpn.vim b/runtime/syntax/openvpn.vim new file mode 100644 index 0000000000..02fd24bf39 --- /dev/null +++ b/runtime/syntax/openvpn.vim @@ -0,0 +1,72 @@ +" Vim syntax file +" Language: OpenVPN +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Filenames: *.ovpn +" Last Change: 2022 Oct 16 + +if exists('b:current_syntax') + finish +endif + +let s:cpo_save = &cpoptions +set cpoptions&vim + +" Options +syntax match openvpnOption /^[a-z-]\+/ + \ skipwhite nextgroup=openvpnArgList +syntax match openvpnArgList /.*$/ transparent contained + \ contains=openvpnArgument,openvpnNumber, + \ openvpnIPv4Address,openvpnIPv6Address, + \ openvpnSignal,openvpnComment + +" Arguments +syntax match openvpnArgument /[^\\"' \t]\+/ + \ contained contains=openvpnEscape +syntax region openvpnArgument matchgroup=openvpnQuote + \ start=/"/ skip=/\\"/ end=/"/ + \ oneline contained contains=openvpnEscape +syntax region openvpnArgument matchgroup=openvpnQuote + \ start=/'/ skip=/\\'/ end=/'/ + \ oneline contained +syntax match openvpnEscape /\\[\\" \t]/ contained + +" Numbers +syntax match openvpnNumber /\<[1-9][0-9]*\(\.[0-9]\+\)\?\>/ contained + +" Signals +syntax match openvpnSignal /SIG\(HUP\|INT\|TERM\|USER[12]\)/ contained + +" IP addresses +syntax match openvpnIPv4Address /\(\d\{1,3}\.\)\{3}\d\{1,3}/ + \ contained nextgroup=openvpnSlash +syntax match openvpnIPv6Address /\([A-F0-9]\{1,4}:\)\{7}\[A-F0-9]\{1,4}/ + \ contained nextgroup=openvpnSlash +syntax match openvpnSlash "/" contained + \ nextgroup=openvpnIPv4Address,openvpnIPv6Address,openvpnNumber + +" Inline files +syntax region openvpnInline matchgroup=openvpnTag + \ start=+^<\z([a-z-]\+\)>+ end=+^</\z1>+ + +" Comments +syntax keyword openvpnTodo contained TODO FIXME NOTE XXX +syntax match openvpnComment /^[;#].*$/ contains=openvpnTodo +syntax match openvpnComment /\s\+\zs[;#].*$/ contains=openvpnTodo + +hi def link openvpnArgument String +hi def link openvpnComment Comment +hi def link openvpnEscape SpecialChar +hi def link openvpnIPv4Address Constant +hi def link openvpnIPv6Address Constant +hi def link openvpnNumber Number +hi def link openvpnOption Keyword +hi def link openvpnQuote Quote +hi def link openvpnSignal Special +hi def link openvpnSlash Delimiter +hi def link openvpnTag Tag +hi def link openvpnTodo Todo + +let b:current_syntax = 'openvpn' + +let &cpoptions = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 6ace5ffef3..26c0e9343c 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -619,7 +619,7 @@ syn match vimCtrlChar "[--]" " Beginners - Patterns that involve ^ {{{2 " ========= -syn match vimLineComment +^[ \t:]*"\("[^"]*"\|[^"]\)*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle +syn match vimLineComment +^[ \t:]*".*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vim9LineComment +^[ \t:]\+#.*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vimCommentTitle '"\s*\%([sS]:\|\h\w*#\)\=\u\w*\(\s\+\u\w*\)*:'hs=s+1 contained contains=vimCommentTitleLeader,vimTodo,@vimCommentGroup syn match vimContinue "^\s*\\" diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index afc045dc96..e5251b7f25 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -339,6 +339,7 @@ local function validate_link(node, bufnr, fname) return helppage, tagname, ignored end +-- TODO: port the logic from scripts/check_urls.vim local function validate_url(text, fname) local ignored = false if vim.fs.basename(fname) == 'pi_netrw.txt' then diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 16326cfe66..87a8e62503 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -86,11 +86,28 @@ local function validate_commit(commit_message) vim.inspect(allowed_types)) end - -- Check if scope is empty + -- Check if scope is appropriate if before_colon:match("%(") then local scope = vim.trim(before_colon:match("%((.*)%)")) + if scope == '' then - return [[Scope can't be empty.]] + return [[Scope can't be empty]] + end + + if vim.startswith(scope, "nvim_") then + return [[Scope should be "api" instead of "nvim_..."]] + end + + local alternative_scope = { + ['filetype.vim'] = 'filetype', + ['filetype.lua'] = 'filetype', + ['tree-sitter'] = 'treesitter', + ['ts'] = 'treesitter', + ['hl'] = 'highlight', + } + + if alternative_scope[scope] then + return ('Scope should be "%s" instead of "%s"'):format(alternative_scope[scope], scope) end end diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index 86afe97a7e..83bf213f40 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -497,14 +497,14 @@ function TLua2DoX_filter.readfile(this, AppStamp, Filename) else this:warning(inStream:getLineNo(), 'something weird here') end - fn_magic = nil -- mustn't indavertently use it again + fn_magic = nil -- mustn't inadvertently use it again -- TODO: If we can make this learn how to generate these, that would be helpful. -- elseif string.find(line, "^M%['.*'%] = function") then -- state = 'in_function' -- it's a function -- outStream:writeln("function textDocument/publishDiagnostics(...){}") - -- fn_magic = nil -- mustn't indavertently use it again + -- fn_magic = nil -- mustn't inadvertently use it again else state = '' -- unknown if #line > 0 then -- we don't know what this line means, so just comment it out diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index 610c20eb48..97757c0848 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -328,7 +328,7 @@ realdir() {( patch_sources() {( local tgt="$1" ; shift - local only_bulid="${1}" ; shift + local only_build="${1}" ; shift get_pvs_comment "$tgt" diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index fbade1b4f7..0854c2f627 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -35,7 +35,7 @@ usage() { echo " -m {vim-revision} List previous (older) missing Vim patches." echo " -M List all merged patch-numbers (at current v:version)." echo " -p {vim-revision} Download and generate a Vim patch. vim-revision" - echo " can be a Vim version (8.0.xxx) or a Git hash." + echo " can be a Vim version (8.1.xxx) or a Git hash." echo " -P {vim-revision} Download, generate and apply a Vim patch." echo " -g {vim-revision} Download a Vim patch." echo " -s [pr args] Create a vim-patch pull request." @@ -120,9 +120,9 @@ get_vim_sources() { commit_message() { if [[ -n "$vim_tag" ]]; then - printf '%s\n%s' "${vim_message}" "${vim_commit_url}" + printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthor}" else - printf 'vim-patch:%s\n\n%s\n%s' "$vim_version" "$vim_message" "$vim_commit_url" + printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthor" fi } @@ -175,6 +175,7 @@ assign_commit_details() { vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" vim_message="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:%B' "${vim_commit}" \ | sed -e 's/\(#[0-9]\{1,\}\)/vim\/vim\1/g')" + vim_coauthor="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")" if [[ ${munge_commit_line} == "true" ]]; then # Remove first line of commit message. vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')" @@ -278,6 +279,35 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" } +uncrustify_patch() { + git diff --quiet || { + >&2 echo 'Vim source working tree dirty, aborting.' + exit 1 + } + + local patch_path=$NVIM_SOURCE_DIR/build/vim_patch + rm -rf "$patch_path" + mkdir -p "$patch_path"/{a,b} + + local commit="$1" + for file in $(git diff-tree --name-only --no-commit-id -r --diff-filter=a "$commit"); do + git --work-tree="$patch_path"/a checkout --quiet "$commit"~ -- "$file" + done + for file in $(git diff-tree --name-only --no-commit-id -r --diff-filter=d "$commit"); do + git --work-tree="$patch_path"/b checkout --quiet "$commit" -- "$file" + done + git reset --quiet --hard HEAD + + # If the difference are drastic enough uncrustify may need to be used more + # than once. This is obviously a bug that needs to be fixed on uncrustify's + # end, but in the meantime this workaround is sufficient. + for _ in {1..2}; do + uncrustify -c "$NVIM_SOURCE_DIR"/src/uncrustify.cfg -q --replace --no-backup "$patch_path"/{a,b}/src/*.[ch] + done + + (cd "$patch_path" && (git --no-pager diff --no-index --no-prefix --patch --unified=5 --color=never a/ b/ || true)) +} + get_vimpatch() { get_vim_sources @@ -286,7 +316,11 @@ get_vimpatch() { msg_ok "Found Vim revision '${vim_commit}'." local patch_content - patch_content="$(git --no-pager show --unified=5 --color=never -1 --pretty=medium "${vim_commit}")" + if check_executable uncrustify; then + patch_content="$(uncrustify_patch "${vim_commit}")" + else + patch_content="$(git --no-pager show --unified=5 --color=never -1 --pretty=medium "${vim_commit}")" + fi cd "${NVIM_SOURCE_DIR}" @@ -470,7 +504,7 @@ submit_pr() { # Gets all Vim commits since the "start" commit. list_vim_commits() { ( - cd "${VIM_SOURCE_DIR}" && git log --reverse v8.0.0000..HEAD "$@" + cd "${VIM_SOURCE_DIR}" && git log --reverse v8.1.0000..HEAD "$@" ) } # Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log. @@ -488,7 +522,7 @@ list_vimpatch_tokens() { list_vimpatch_numbers() { # Transform "vim-patch:X.Y.ZZZZ" to "ZZZZ". list_vimpatch_tokens | while read -r vimpatch_token; do - echo "$vimpatch_token" | grep '8\.0\.' | sed 's/.*vim-patch:8\.0\.\([0-9a-z]\+\).*/\1/' + echo "$vimpatch_token" | grep '8\.1\.' | sed -E 's/.*vim-patch:8\.1\.([0-9a-z]+).*/\1/' done } @@ -585,7 +619,7 @@ _set_missing_vimpatches() { else info=${line#* } if [[ -n $info ]]; then - # Remove any "patch 8.0.0902: " prefixes, and prefix with ": ". + # Remove any "patch 8.1.0902: " prefixes, and prefix with ": ". info=": ${info#patch*: }" fi fi @@ -628,7 +662,7 @@ show_vimpatches() { Instructions: To port one of the above patches to Neovim, execute this script with the patch revision as argument and follow the instructions, e.g. - '${BASENAME} -p v8.0.1234', or '${BASENAME} -P v8.0.1234' + '${BASENAME} -p v8.1.1234', or '${BASENAME} -P v8.1.1234' NOTE: Please port the _oldest_ patch if you possibly can. You can use '${BASENAME} -l path/to/file' to see what patches are missing for a file. diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua index 11eb285462..836f672f6e 100755 --- a/scripts/vimpatch.lua +++ b/scripts/vimpatch.lua @@ -1,7 +1,7 @@ -- Updates version.c list of applied Vim patches. -- -- Usage: --- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -i NONE -u NONE --headless +'luafile ./scripts/vimpatch.lua' +q +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -V1 -es -i NONE +'luafile ./scripts/vimpatch.lua' +q local nvim = vim.api @@ -22,12 +22,13 @@ end -- Generates the lines to be inserted into the src/version.c -- `included_patches[]` definition. local function gen_version_c_lines() - -- Set of merged Vim 8.0.zzzz patch numbers. + -- Set of merged Vim 8.1.zzzz patch numbers. local merged_patch_numbers = {} local highest = 0 for _, n in ipairs(vimpatch_sh_list_numbers()) do + n = tonumber(n) if n then - merged_patch_numbers[tonumber(n)] = true + merged_patch_numbers[n] = true highest = math.max(highest, n) end end diff --git a/src/clint.py b/src/clint.py index 4cdcc46bb3..155f5f2ce5 100755 --- a/src/clint.py +++ b/src/clint.py @@ -157,7 +157,6 @@ _ERROR_CATEGORIES = [ 'build/printf_format', 'build/storage_class', 'readability/bool', - 'readability/braces', 'readability/multiline_comment', 'readability/multiline_string', 'readability/nul', @@ -2305,213 +2304,36 @@ def CheckBraces(filename, clean_lines, linenum, error): prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] if (not Search(r'[,;:}{(]\s*$', prevline) and not Match(r'\s*#', prevline)): - error(filename, linenum, 'whitespace/braces', 4, - '{ should almost always be at the end' - ' of the previous line') + return # Brace must appear after function signature, but on the *next* line if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line): pos = line.find('(') - (endline, end_linenum, endpos) = CloseExpression( - clean_lines, linenum, pos) + (endline, end_linenum, _) = CloseExpression(clean_lines, linenum, pos) if endline.endswith('{'): - error(filename, end_linenum, 'readability/braces', 5, - 'Brace starting function body must be placed on its own line') - else: - func_start_linenum = end_linenum + 1 - while not clean_lines.lines[func_start_linenum] == "{": - attrline = Match( - r'^((?!# *define).*?)' - r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' - r'(?:\(\d+(, \d+)*\))?', - clean_lines.lines[func_start_linenum], - ) - if attrline: - if len(attrline.group(1)) != 2: - error(filename, func_start_linenum, - 'whitespace/indent', 5, - 'Function attribute line should have 2-space ' - 'indent') - - func_start_linenum += 1 - else: - func_start = clean_lines.lines[func_start_linenum] - if not func_start.startswith('enum ') and func_start.endswith('{'): - error(filename, func_start_linenum, - 'readability/braces', 5, - 'Brace starting function body must be placed ' - 'after the function signature') - break - - # An else clause should be on the same line as the preceding closing brace. - # If there is no preceding closing brace, there should be one. - if Match(r'\s*else\s*', line): - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if Match(r'\s*}\s*$', prevline): - error(filename, linenum, 'whitespace/newline', 4, - 'An else should appear on the same line as the preceding }') - else: - error(filename, linenum, 'readability/braces', 5, - 'An else should always have braces before it') - - # If should always have a brace - for blockstart in ('if', 'while', 'for'): - if Match(r'\s*{0}(?!\w)[^{{]*$'.format(blockstart), line): - pos = line.find(blockstart) - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - '{} should always use braces'.format(blockstart)) - - # If braces come on one side of an else, they should be on both. - # However, we have to worry about "else if" that spans multiple lines! - if Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): - if Search(r'}\s*else if([^{]*)$', line): # could be multi-line if - # find the ( after the if - pos = line.find('else if') - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - # must be brace after if - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - else: # common case: else not followed by a multi-line if - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - - # Likewise, an else should never have the else clause on the same line - if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): - error(filename, linenum, 'whitespace/newline', 4, - 'Else clause should never be on same line as else (use 2 lines)') - - # In the same way, a do/while should never be on one line - if Match(r'\s*do [^\s{]', line): - error(filename, linenum, 'whitespace/newline', 4, - 'do/while clauses should not be on a single line') - - # Block bodies should not be followed by a semicolon. Due to C++11 - # brace initialization, there are more places where semicolons are - # required than not, so we use a whitelist approach to check these - # rather than a blacklist. These are the places where "};" should - # be replaced by just "}": - # 1. Some flavor of block following closing parenthesis: - # for (;;) {}; - # while (...) {}; - # switch (...) {}; - # Function(...) {}; - # if (...) {}; - # if (...) else if (...) {}; - # - # 2. else block: - # if (...) else {}; - # - # 3. const member function: - # Function(...) const {}; - # - # 4. Block following some statement: - # x = 42; - # {}; - # - # 5. Block at the beginning of a function: - # Function(...) { - # {}; - # } - # - # Note that naively checking for the preceding "{" will also match - # braces inside multi-dimensional arrays, but this is fine since - # that expression will not contain semicolons. - # - # 6. Block following another block: - # while (true) {} - # {}; - # - # 7. End of namespaces: - # namespace {}; - # - # These semicolons seems far more common than other kinds of - # redundant semicolons, possibly due to people converting classes - # to namespaces. For now we do not warn for this case. - # - # Try matching case 1 first. - match = Match(r'^(.*\)\s*)\{', line) - if match: - # Matched closing parenthesis (case 1). Check the token before the - # matching opening parenthesis, and don't warn if it looks like a - # macro. This avoids these false positives: - # - macro that defines a base class - # - multi-line macro that defines a base class - # - macro that defines the whole class-head - # - # But we still issue warnings for macros that we know are safe to - # warn, specifically: - # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P - # - TYPED_TEST - # - INTERFACE_DEF - # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: - # - # We implement a whitelist of safe macros instead of a blacklist of - # unsafe macros, even though the latter appears less frequently in - # google code and would have been easier to implement. This is because - # the downside for getting the whitelist wrong means some extra - # semicolons, while the downside for getting the blacklist wrong - # would result in compile errors. - # - # In addition to macros, we also don't want to warn on compound - # literals. - closing_brace_pos = match.group(1).rfind(')') - opening_parenthesis = ReverseCloseExpression( - clean_lines, linenum, closing_brace_pos) - if opening_parenthesis[2] > -1: - line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] - macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) - if ((macro and - macro.group(1) not in ( - 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', - 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', - 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or - Search(r'\s+=\s*$', line_prefix) or - Search(r'^\s*return\s*$', line_prefix)): - match = None - - else: - # Try matching cases 2-3. - match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) - if not match: - # Try matching cases 4-6. These are always matched on separate - # lines. - # - # Note that we can't simply concatenate the previous line to the - # current line and do a single match, otherwise we may output - # duplicate warnings for the blank line case: - # if (cond) { - # // blank line - # } - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and Search(r'[;{}]\s*$', prevline): - match = Match(r'^(\s*)\{', line) - - # Check matching closing brace - if match: - (endline, endlinenum, endpos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - if endpos > -1 and Match(r'^\s*;', endline[endpos:]): - # Current {} pair is eligible for semicolon check, and we have found - # the redundant semicolon, output warning here. - # - # Note: because we are scanning forward for opening braces, and - # outputting warnings for the matching closing brace, if there are - # nested blocks with trailing semicolons, we will get the error - # messages in reversed order. - error(filename, endlinenum, 'readability/braces', 4, - "You don't need a ; after a }") + return + func_start_linenum = end_linenum + 1 + while not clean_lines.lines[func_start_linenum] == "{": + attrline = Match( + r'^((?!# *define).*?)' + r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' + r'(?:\(\d+(, \d+)*\))?', + clean_lines.lines[func_start_linenum], + ) + if attrline: + if len(attrline.group(1)) != 2: + error(filename, func_start_linenum, + 'whitespace/indent', 5, + 'Function attribute line should have 2-space ' + 'indent') + + func_start_linenum += 1 + else: + func_start = clean_lines.lines[func_start_linenum] + if not func_start.startswith('enum ') and func_start.endswith('{'): + return + break def CheckStyle(filename, clean_lines, linenum, error): """Checks rules from the 'C++ style rules' section of cppguide.html. @@ -2681,8 +2503,7 @@ def CheckLanguage(filename, clean_lines, linenum, error): # Check for suspicious usage of "if" like # } if (a == b) { if Search(r'\}\s*if\s*\(', line): - error(filename, linenum, 'readability/braces', 4, - 'Did you mean "else if"? If not, start a new line for "if".') + return # Check for potential format string bugs like printf(foo). # We constrain the pattern not to pick things like DocidForPrintf(foo). diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index ac27f46bfb..aee6319770 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -571,7 +571,10 @@ file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) # install treesitter parser if bundled if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) - file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR}) + glob_wrapper(TREESITTER_PARSERS ${DEPS_PREFIX}/lib/nvim/parser/*) + foreach(parser_lib ${TREESITTER_PARSERS}) + file(COPY ${parser_lib} DESTINATION ${BINARY_LIB_DIR}/parser) + endforeach() endif() install(DIRECTORY ${BINARY_LIB_DIR} diff --git a/src/nvim/README.md b/src/nvim/README.md index 91fb3ca2f6..e710c3ef58 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -391,8 +391,8 @@ implemented by libuv, the platform layer used by Nvim. Since Nvim inherited its code from Vim, the states are not prepared to receive "arbitrary events", so we use a special key to represent those (When a state -receives an "arbitrary event", it normally doesn't do anything other update the -screen). +receives an "arbitrary event", it normally doesn't do anything other than +update the screen). Main loop --------- diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index b5c695b9ce..3dfe77ba38 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -401,6 +401,13 @@ cleanup: /// pattern = { "*.py", "*.pyi" } /// </pre> /// +/// Note: The `pattern` is passed to callbacks and commands as a literal string; environment +/// variables like `$HOME` and `~` are not automatically expanded as they are by |:autocmd|. +/// Instead, |expand()| such variables explicitly: +/// <pre> +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" +/// </pre> +/// /// Example values for event: /// <pre> /// "BufWritePre" @@ -411,7 +418,7 @@ cleanup: /// @param opts Dictionary of autocommand options: /// - group (string|integer) optional: the autocommand group name or /// id to match against. -/// - pattern (string|array) optional: pattern or patterns to match +/// - pattern (string|array) optional: pattern or patterns to match literally /// against |autocmd-pattern|. /// - buffer (integer) optional: buffer number for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern}. diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 2ae3ee6c7c..8693751c97 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -48,7 +48,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(const uint64_t channel_id) +static inline bool is_internal_call(uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index f92b205531..f0161a53a8 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -3,10 +3,7 @@ #include "nvim/api/private/defs.h" -typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, - Array args, - Arena *arena, - Error *error); +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error); /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index e6d8cb2fdb..f251e0043f 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -845,7 +845,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || strcmp(chunk[i], chunk[i + 1])) { + || strcmp(chunk[i], chunk[i + 1]) != 0) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 17930dca85..21400862b9 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -69,11 +69,10 @@ void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; // Second revision of the grid protocol, used with ext_linegrid ui option -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, + Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; -void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; void hl_group_set(String name, Integer id) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; @@ -85,8 +84,8 @@ void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; -void grid_scroll(Integer grid, Integer top, Integer bot, - Integer left, Integer right, Integer rows, Integer cols) +void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, + Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -94,20 +93,18 @@ void grid_destroy(Integer grid) // For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. -void raw_line(Integer grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - LineFlags flags, const schar_T *chunk, const sattr_T *attrs) +void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; void event(char *name, Array args) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; -void win_pos(Integer grid, Window win, Integer startrow, - Integer startcol, Integer width, Integer height) +void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width, + Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, - Float anchor_row, Float anchor_col, Boolean focusable, - Integer zindex) +void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, Float anchor_row, + Float anchor_col, Boolean focusable, Integer zindex) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_external_pos(Integer grid, Window win) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -118,29 +115,25 @@ void win_close(Integer grid) void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; -void win_viewport(Integer grid, Window win, Integer topline, - Integer botline, Integer curline, Integer curcol, - Integer line_count) +void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline, + Integer curcol, Integer line_count) FUNC_API_SINCE(7) FUNC_API_BRIDGE_IMPL; -void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, - Integer row, Integer col) +void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; -void popupmenu_show(Array items, Integer selected, - Integer row, Integer col, Integer grid) +void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_select(Integer selected) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void tabline_update(Tabpage current, Array tabs, - Buffer current_buffer, Array buffers) +void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array buffers) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_show(Array content, Integer pos, String firstc, String prompt, - Integer indent, Integer level) +void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, + Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0c0c71b694..beb48b8d7d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1012,7 +1012,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) return (Integer)chan->id; } -static void term_write(char *buf, size_t size, void *data) +static void term_write(char *buf, size_t size, void *data) // NOLINT(readability-non-const-parameter) { Channel *chan = data; LuaRef cb = chan->stream.internal.cb; @@ -1685,7 +1685,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er // error handled after loop break; } - // TODO(bfredl): wastefull copy. It could be avoided to encoding to msgpack + // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack // directly here. But `result` might become invalid when next api function // is called in the loop. ADD_C(results, copy_object(result, arena)); @@ -2125,7 +2125,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * bool use_tabline = false; bool highlights = false; - if (str.size < 2 || memcmp(str.data, "%!", 2)) { + if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { const char *const errmsg = check_stl_option(str.data); if (errmsg) { api_set_error(err, kErrorTypeValidation, "%s", errmsg); diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index b1241166bf..96d6fe214a 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -84,31 +84,31 @@ # define PATHSEPSTR "/" #endif -static inline bool ascii_iswhite(int) +static inline bool ascii_iswhite(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_iswhite_or_nul(int) +static inline bool ascii_iswhite_or_nul(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isdigit(int) +static inline bool ascii_isdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isxdigit(int) +static inline bool ascii_isxdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isident(int) +static inline bool ascii_isident(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isbdigit(int) +static inline bool ascii_isbdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isspace(int) +static inline bool ascii_isspace(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 8016904702..ade5c35450 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -689,6 +689,8 @@ void buf_clear_file(buf_T *buf) { buf->b_ml.ml_line_count = 1; unchanged(buf, true, true); + buf->b_p_eof = false; + buf->b_start_eof = false; buf->b_p_eol = true; buf->b_start_eol = true; buf->b_p_bomb = false; diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index d56a70dc7e..7380bb045a 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -69,8 +69,7 @@ EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); # include "buffer.h.generated.h" #endif -static inline void buf_set_changedtick(buf_T *const buf, - const varnumber_T changedtick) +static inline void buf_set_changedtick(buf_T *buf, varnumber_T changedtick) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Set b:changedtick, also checking b: for consistency in debug build @@ -102,7 +101,7 @@ static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T chang } } -static inline varnumber_T buf_get_changedtick(const buf_T *const buf) +static inline varnumber_T buf_get_changedtick(const buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -116,7 +115,7 @@ static inline varnumber_T buf_get_changedtick(const buf_T *const buf) return buf->changedtick_di.di_tv.vval.v_number; } -static inline void buf_inc_changedtick(buf_T *const buf) +static inline void buf_inc_changedtick(buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Increment b:changedtick value diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 043a31bf16..4e46a1aef2 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -675,6 +675,7 @@ struct file_buffer { char *b_p_cfu; ///< 'completefunc' char *b_p_ofu; ///< 'omnifunc' char *b_p_tfu; ///< 'tagfunc' + int b_p_eof; ///< 'endoffile' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' @@ -793,6 +794,7 @@ struct file_buffer { linenr_T b_no_eol_lnum; // non-zero lnum when last line of next binary // write should not have an end-of-line + int b_start_eof; // last line had eof (CTRL-Z) when it was read int b_start_eol; // last line had eol when it was read int b_start_ffc; // first char of 'ff' when edit started char *b_start_fenc; // 'fileencoding' when edit started or NULL @@ -892,6 +894,8 @@ struct diffblock_S { diff_T *df_next; linenr_T df_lnum[DB_COUNT]; // line number in buffer linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines + bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into + // smaller diff hunks? }; #define SNAP_HELP_IDX 0 @@ -959,7 +963,8 @@ struct frame_S { // for first // fr_child and fr_win are mutually exclusive frame_T *fr_child; // first contained frame - win_T *fr_win; // window that fills this frame + win_T *fr_win; // window that fills this frame; for a snapshot + // set to the current window }; #define FR_LEAF 0 // frame is a leaf @@ -1336,6 +1341,7 @@ struct window_S { int w_briopt_shift; // additional shift for breakindent bool w_briopt_sbr; // sbr in 'briopt' int w_briopt_list; // additional indent for lists + int w_briopt_vcol; // indent for specific column // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) @@ -1402,7 +1408,9 @@ struct window_S { }; /// Macros defined in Vim, but not in Neovim +// uncrustify:off #define CHANGEDTICK(buf) \ (=== Include buffer.h & use buf_(get|set|inc) _changedtick ===) +// uncrustify:on #endif // NVIM_BUFFER_DEFS_H diff --git a/src/nvim/change.c b/src/nvim/change.c index 2424a8a2eb..c6df98651d 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -539,6 +539,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) void save_file_ff(buf_T *buf) { buf->b_start_ffc = (unsigned char)(*buf->b_p_ff); + buf->b_start_eof = buf->b_p_eof; buf->b_start_eol = buf->b_p_eol; buf->b_start_bomb = buf->b_p_bomb; @@ -573,7 +574,8 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty) if (buf->b_start_ffc != *buf->b_p_ff) { return true; } - if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { + if ((buf->b_p_bin || !buf->b_p_fixeol) + && (buf->b_start_eof != buf->b_p_eof || buf->b_start_eol != buf->b_p_eol)) { return true; } if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index a8a5848c71..480f3aa18c 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -1246,7 +1246,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons arg = (const char *)skipwhite(skiptowhite(arg)); if (*arg != NUL) { xp->xp_context = EXPAND_NOTHING; - arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), p_magic, NULL); + arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), p_magic); } } return (const char *)find_nextcmd(arg); @@ -1285,7 +1285,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons if (delim) { // Skip "from" part. arg++; - arg = (const char *)skip_regexp((char *)arg, delim, p_magic, NULL); + arg = (const char *)skip_regexp((char *)arg, delim, p_magic); } // Skip "to" part. while (arg[0] != NUL && (uint8_t)arg[0] != delim) { @@ -1880,7 +1880,7 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline /// @param col position of cursor /// @param matchcount return: nr of matches /// @param matches return: array of pointers to matches -int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***matches) +int expand_cmdline(expand_T *xp, const char_u *str, int col, int *matchcount, char ***matches) { char_u *file_str = NULL; int options = WILD_ADD_SLASH|WILD_SILENT; @@ -2351,7 +2351,7 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag } // Go over all directories in $PATH. Expand matches in that directory and - // collect them in "ga". When "." is not in $PATH also expaned for the + // collect them in "ga". When "." is not in $PATH also expand for the // current directory, to find "subdir/cmd". ga_init(&ga, (int)sizeof(char *), 10); hashtab_T found_ht; diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 6f4f17659a..68522bdaa0 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -19,8 +19,7 @@ #endif /// Handling of cursor and mouse pointer shapes in various modes. -cursorentry_T shape_table[SHAPE_IDX_COUNT] = -{ +cursorentry_T shape_table[SHAPE_IDX_COUNT] = { // Values are set by 'guicursor' and 'mouseshape'. // Adjust the SHAPE_IDX_ defines when changing this! { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE }, diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 9446d35630..503e546562 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -26,6 +26,8 @@ #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/linematch.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -61,10 +63,12 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #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 DIFF_LINEMATCH 0x1000 // match most similar lines within diff #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static long diff_algorithm = 0; +static int linematch_lines = 0; #define LBUFLEN 50 // length of line in diff file @@ -74,13 +78,13 @@ static TriState diff_a_works = kNone; // used for diff input typedef struct { - char_u *din_fname; // used for external diff + char *din_fname; // used for external diff mmfile_t din_mmfile; // used for internal diff } diffin_T; // used for diff result typedef struct { - char_u *dout_fname; // used for external diff + char *dout_fname; // used for external diff garray_T dout_ga; // used for internal diff } diffout_T; @@ -100,6 +104,12 @@ typedef struct { int dio_internal; // using internal diff } diffio_T; +typedef enum { + DIFF_ED, + DIFF_UNIFIED, + DIFF_NONE, +} diffstyle_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "diff.c.generated.h" #endif @@ -204,13 +214,7 @@ static void diff_buf_clear(void) /// @return Its index or DB_COUNT if not found. static int diff_buf_idx(buf_T *buf) { - int idx; - for (idx = 0; idx < DB_COUNT; idx++) { - if (curtab->tp_diffbuf[idx] == buf) { - break; - } - } - return idx; + return diff_buf_idx_tp(buf, curtab); } /// Find buffer "buf" in the list of diff buffers for tab page "tp". @@ -309,8 +313,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T diff_T *dp = tp->tp_first_diff; linenr_T lnum_deleted = line1; // lnum of remaining deletion - linenr_T n; - linenr_T off; for (;;) { // If the change is after the previous diff block and before the next // diff block, thus not touching an existing change, create a new diff @@ -363,7 +365,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T if (last >= line1 - 1) { // 6. change below line2: only adjust for amount_after; also when // "deleted" became zero when deleted all lines between two diffs. - if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2) { + if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2 - dp->is_linematched) { if (amount_after == 0) { // nothing left to change break; @@ -374,7 +376,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // 2. 3. 4. 5.: inserted/deleted lines touching this diff. if (deleted > 0) { - off = 0; + linenr_T n; + linenr_T off = 0; if (dp->df_lnum[idx] >= line1) { if (last <= line2) { // 4. delete all lines of diff @@ -454,7 +457,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } // check if this block touches the previous one, may merge them. - if ((dprev != NULL) + if ((dprev != NULL) && !dp->is_linematched && (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) { int i; for (i = 0; i < DB_COUNT; i++) { @@ -462,9 +465,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T dprev->df_count[i] += dp->df_count[i]; } } - dprev->df_next = dp->df_next; - xfree(dp); - dp = dprev->df_next; + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -485,15 +486,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } if (i == DB_COUNT) { - diff_T *dnext = dp->df_next; - xfree(dp); - dp = dnext; - - if (dprev == NULL) { - tp->tp_first_diff = dnext; - } else { - dprev->df_next = dnext; - } + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -523,6 +516,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) { diff_T *dnew = xmalloc(sizeof(*dnew)); + dnew->is_linematched = false; dnew->df_next = dp; if (dprev == NULL) { tp->tp_first_diff = dnew; @@ -533,6 +527,20 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) return dnew; } +static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp) +{ + diff_T *ret = dp->df_next; + xfree(dp); + + if (dprev == NULL) { + tp->tp_first_diff = ret; + } else { + dprev->df_next = ret; + } + + return ret; +} + /// Check if the diff block "dp" can be made smaller for lines at the start and /// end that are equal. Called after inserting lines. /// @@ -704,16 +712,16 @@ static void clear_diffin(diffin_T *din) if (din->din_fname == NULL) { XFREE_CLEAR(din->din_mmfile.ptr); } else { - os_remove((char *)din->din_fname); + os_remove(din->din_fname); } } static void clear_diffout(diffout_T *dout) { if (dout->dout_fname == NULL) { - ga_clear_strings(&dout->dout_ga); + ga_clear(&dout->dout_ga); } else { - os_remove((char *)dout->dout_fname); + os_remove(dout->dout_fname); } } @@ -723,15 +731,19 @@ static void clear_diffout(diffout_T *dout) /// @param din /// /// @return FAIL for failure. -static int diff_write_buffer(buf_T *buf, diffin_T *din) +static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T end) { - long len = 0; + size_t len = 0; + + if (end < 0) { + end = buf->b_ml.ml_line_count; + } // xdiff requires one big block of memory with all the text. - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - len += (long)strlen(ml_get_buf(buf, lnum, false)) + 1; + for (linenr_T lnum = start; lnum <= end; lnum++) { + len += strlen(ml_get_buf(buf, lnum, false)) + 1; } - char_u *ptr = try_malloc((size_t)len); + char *ptr = try_malloc(len); if (ptr == NULL) { // Allocating memory failed. This can happen, because we try to read // the whole buffer text into memory. Set the failed flag, the diff @@ -745,37 +757,32 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) } return FAIL; } - din->din_mmfile.ptr = (char *)ptr; - din->din_mmfile.size = len; + m->ptr = ptr; + m->size = (long)len; len = 0; - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (char_u *s = (char_u *)ml_get_buf(buf, lnum, false); *s != NUL;) { - if (diff_flags & DIFF_ICASE) { - int c; + for (linenr_T lnum = start; lnum <= end; lnum++) { + char *s = ml_get_buf(buf, lnum, false); + if (diff_flags & DIFF_ICASE) { + while (*s != NUL) { char cbuf[MB_MAXBYTES + 1]; - if (*s == NL) { - c = NUL; - } else { - // xdiff doesn't support ignoring case, fold-case the text. - c = utf_ptr2char((char *)s); - c = utf_fold(c); - } - const int orig_len = utfc_ptr2len((char *)s); - if (utf_char2bytes(c, (char *)cbuf) != orig_len) { - // TODO(Bram): handle byte length difference - memmove(ptr + len, s, (size_t)orig_len); - } else { - memmove(ptr + len, cbuf, (size_t)orig_len); - } + // xdiff doesn't support ignoring case, fold-case the text. + int c = *s == NL ? NUL : utf_fold(utf_ptr2char(s)); + const int orig_len = utfc_ptr2len(s); + // TODO(Bram): handle byte length difference + char *s1 = (utf_char2bytes(c, cbuf) != orig_len) ? s : cbuf; + memmove(ptr + len, s1, (size_t)orig_len); s += orig_len; - len += orig_len; - } else { - ptr[len++] = *s == NL ? NUL : *s; - s++; + len += (size_t)orig_len; } + } else { + size_t slen = strlen(s); + memmove(ptr + len, s, slen); + // NUL is represented as NL; convert + memchrsub(ptr + len, NL, NUL, slen); + len += slen; } ptr[len++] = NL; } @@ -793,7 +800,7 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) static int diff_write(buf_T *buf, diffin_T *din) { if (din->din_fname == NULL) { - return diff_write_buffer(buf, din); + return diff_write_buffer(buf, &din->din_mmfile, 1, -1); } // Always use 'fileformat' set to "unix". @@ -803,7 +810,7 @@ static int diff_write(buf_T *buf, diffin_T *din) // Writing the buffer is an implementation detail of performing the diff, // so it shouldn't update the '[ and '] marks. cmdmod.cmod_flags |= CMOD_LOCKMARKS; - int r = buf_write(buf, (char *)din->din_fname, NULL, + int r = buf_write(buf, din->din_fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, false, false, true); cmdmod.cmod_flags = save_cmod_flags; @@ -821,12 +828,12 @@ static int diff_write(buf_T *buf, diffin_T *din) static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) { if (dio->dio_internal) { - ga_init(&dio->dio_diff.dout_ga, sizeof(char *), 1000); + ga_init(&dio->dio_diff.dout_ga, sizeof(diffhunk_T), 100); } else { // We need three temp file names. - dio->dio_orig.din_fname = (char_u *)vim_tempname(); - dio->dio_new.din_fname = (char_u *)vim_tempname(); - dio->dio_diff.dout_fname = (char_u *)vim_tempname(); + dio->dio_orig.din_fname = vim_tempname(); + dio->dio_new.din_fname = vim_tempname(); + dio->dio_diff.dout_fname = vim_tempname(); if (dio->dio_orig.din_fname == NULL || dio->dio_new.din_fname == NULL || dio->dio_diff.dout_fname == NULL) { @@ -992,7 +999,7 @@ static int check_external_diff(diffio_T *diffio) TriState ok = kFalse; for (;;) { ok = kFalse; - FILE *fd = os_fopen((char *)diffio->dio_orig.din_fname, "w"); + FILE *fd = os_fopen(diffio->dio_orig.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1001,7 +1008,7 @@ static int check_external_diff(diffio_T *diffio) io_error = true; } fclose(fd); - fd = os_fopen((char *)diffio->dio_new.din_fname, "w"); + fd = os_fopen(diffio->dio_new.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1012,18 +1019,18 @@ static int check_external_diff(diffio_T *diffio) fclose(fd); fd = NULL; if (diff_file(diffio) == OK) { - fd = os_fopen((char *)diffio->dio_diff.dout_fname, "r"); + fd = os_fopen(diffio->dio_diff.dout_fname, "r"); } if (fd == NULL) { io_error = true; } else { - char_u linebuf[LBUFLEN]; + char linebuf[LBUFLEN]; for (;;) { // For normal diff there must be a line that contains // "1c1". For unified diff "@@ -1 +1 @@". - if (vim_fgets(linebuf, LBUFLEN, fd)) { + if (vim_fgets((char_u *)linebuf, LBUFLEN, fd)) { break; } @@ -1034,10 +1041,10 @@ static int check_external_diff(diffio_T *diffio) } fclose(fd); } - os_remove((char *)diffio->dio_diff.dout_fname); - os_remove((char *)diffio->dio_new.din_fname); + os_remove(diffio->dio_diff.dout_fname); + os_remove(diffio->dio_new.din_fname); } - os_remove((char *)diffio->dio_orig.din_fname); + os_remove(diffio->dio_orig.din_fname); } // When using 'diffexpr' break here. @@ -1115,9 +1122,9 @@ static int diff_file_internal(diffio_T *diffio) /// @return OK or FAIL static int diff_file(diffio_T *dio) { - char *tmp_orig = (char *)dio->dio_orig.din_fname; - char *tmp_new = (char *)dio->dio_new.din_fname; - char *tmp_diff = (char *)dio->dio_diff.dout_fname; + char *tmp_orig = dio->dio_orig.din_fname; + char *tmp_new = dio->dio_new.din_fname; + char *tmp_diff = dio->dio_diff.dout_fname; if (*p_dex != NUL) { // Use 'diffexpr' to generate the diff file. eval_diff(tmp_orig, tmp_new, tmp_diff); @@ -1167,10 +1174,10 @@ static int diff_file(diffio_T *dio) /// @param eap void ex_diffpatch(exarg_T *eap) { - char_u *buf = NULL; + char *buf = NULL; win_T *old_curwin = curwin; char *newname = NULL; // name of patched file buffer - char_u *esc_name = NULL; + char *esc_name = NULL; #ifdef UNIX char *fullname = NULL; @@ -1178,16 +1185,16 @@ void ex_diffpatch(exarg_T *eap) // We need two temp file names. // Name of original temp file. - char_u *tmp_orig = (char_u *)vim_tempname(); + char *tmp_orig = vim_tempname(); // Name of patched temp file. - char_u *tmp_new = (char_u *)vim_tempname(); + char *tmp_new = vim_tempname(); if ((tmp_orig == NULL) || (tmp_new == NULL)) { goto theend; } // Write the current buffer to "tmp_orig". - if (buf_write(curbuf, (char *)tmp_orig, NULL, + if (buf_write(curbuf, tmp_orig, NULL, (linenr_T)1, curbuf->b_ml.ml_line_count, NULL, false, false, false, true) == FAIL) { goto theend; @@ -1197,7 +1204,7 @@ void ex_diffpatch(exarg_T *eap) // Get the absolute path of the patchfile, changing directory below. fullname = FullName_save(eap->arg, false); esc_name = - vim_strsave_shellescape((char_u *)(fullname != NULL ? fullname : eap->arg), true, true); + (char *)vim_strsave_shellescape((char_u *)(fullname != NULL ? fullname : eap->arg), true, true); #else esc_name = vim_strsave_shellescape(eap->arg, true, true); #endif @@ -1205,14 +1212,14 @@ void ex_diffpatch(exarg_T *eap) buf = xmalloc(buflen); #ifdef UNIX - char_u dirbuf[MAXPATHL]; + char dirbuf[MAXPATHL]; // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we // exit (any .rej files created). Don't change directory if we can't // return to the current. - if ((os_dirname(dirbuf, MAXPATHL) != OK) - || (os_chdir((char *)dirbuf) != 0)) { + if ((os_dirname((char_u *)dirbuf, MAXPATHL) != OK) + || (os_chdir(dirbuf) != 0)) { dirbuf[0] = NUL; } else { char *tempdir = vim_gettempdir(); @@ -1227,24 +1234,22 @@ void ex_diffpatch(exarg_T *eap) if (*p_pex != NUL) { // Use 'patchexpr' to generate the new file. #ifdef UNIX - eval_patch((char *)tmp_orig, - (fullname != NULL ? fullname : eap->arg), - (char *)tmp_new); + eval_patch(tmp_orig, (fullname != NULL ? fullname : eap->arg), tmp_new); #else - eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); + eval_patch(tmp_orig, eap->arg, tmp_new); #endif } else { // Build the patch command and execute it. Ignore errors. - vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + vim_snprintf(buf, buflen, "patch -o %s %s < %s", tmp_new, tmp_orig, esc_name); block_autocmds(); // Avoid ShellCmdPost stuff - (void)call_shell(buf, kShellOptFilter, NULL); + (void)call_shell((char_u *)buf, kShellOptFilter, NULL); unblock_autocmds(); } #ifdef UNIX if (dirbuf[0] != NUL) { - if (os_chdir((char *)dirbuf) != 0) { + if (os_chdir(dirbuf) != 0) { emsg(_(e_prev_dir)); } shorten_fnames(true); @@ -1254,14 +1259,14 @@ void ex_diffpatch(exarg_T *eap) // Delete any .orig or .rej file created. STRCPY(buf, tmp_new); STRCAT(buf, ".orig"); - os_remove((char *)buf); + os_remove(buf); STRCPY(buf, tmp_new); STRCAT(buf, ".rej"); - os_remove((char *)buf); + os_remove(buf); // Only continue if the output file was created. FileInfo file_info; - bool info_ok = os_fileinfo((char *)tmp_new, &file_info); + bool info_ok = os_fileinfo(tmp_new, &file_info); uint64_t filesize = os_fileinfo_size(&file_info); if (!info_ok || filesize == 0) { emsg(_("E816: Cannot read patch output")); @@ -1277,7 +1282,7 @@ void ex_diffpatch(exarg_T *eap) if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { // Pretend it was a ":split fname" command eap->cmdidx = CMD_split; - eap->arg = (char *)tmp_new; + eap->arg = tmp_new; do_exedit(eap, old_curwin); // check that split worked and editing tmp_new @@ -1302,12 +1307,12 @@ void ex_diffpatch(exarg_T *eap) theend: if (tmp_orig != NULL) { - os_remove((char *)tmp_orig); + os_remove(tmp_orig); } xfree(tmp_orig); if (tmp_new != NULL) { - os_remove((char *)tmp_new); + os_remove(tmp_new); } xfree(tmp_new); xfree(newname); @@ -1526,210 +1531,219 @@ void ex_diffoff(exarg_T *eap) } } -/// Read the diff output and add each entry to the diff list. -/// -/// @param idx_orig idx of original file -/// @param idx_new idx of new file -/// @dout diff output -static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +static bool extract_hunk_internal(diffout_T *dout, diffhunk_T *hunk, int *line_idx) { - FILE *fd = NULL; - int line_idx = 0; - diff_T *dprev = NULL; - diff_T *dp = curtab->tp_first_diff; - diff_T *dn, *dpl; - diffout_T *dout = &dio->dio_diff; - char linebuf[LBUFLEN]; // only need to hold the diff line - char *line; - linenr_T off; - int i; - int notset = true; // block "*dp" not set yet - diffhunk_T *hunk = NULL; // init to avoid gcc warning - enum { - DIFF_ED, - DIFF_UNIFIED, - DIFF_NONE, - } diffstyle = DIFF_NONE; - - if (dout->dout_fname == NULL) { - diffstyle = DIFF_UNIFIED; - } else { - fd = os_fopen((char *)dout->dout_fname, "r"); - if (fd == NULL) { - emsg(_("E98: Cannot read diff output")); - return; - } - } - - if (!dio->dio_internal) { - hunk = xmalloc(sizeof(*hunk)); + bool eof = *line_idx >= dout->dout_ga.ga_len; + if (!eof) { + *hunk = ((diffhunk_T *)dout->dout_ga.ga_data)[(*line_idx)++]; } + return eof; +} +// Extract hunk by parsing the diff output from file and calculate the diffstyle. +static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle) +{ for (;;) { - if (dio->dio_internal) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; - } else { - if (fd == NULL) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - line = ((char **)dout->dout_ga.ga_data)[line_idx++]; + char line[LBUFLEN]; // only need to hold the diff line + if (vim_fgets((char_u *)line, LBUFLEN, fd)) { + return true; // end of file + } + + if (*diffstyle == DIFF_NONE) { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit(*line)) { + *diffstyle = DIFF_ED; + } else if ((STRNCMP(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; + } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 + && (vim_fgets((char_u *)line, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "+++ ", 4) == 0) + && (vim_fgets((char_u *)line, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; } else { - if (vim_fgets((char_u *)linebuf, LBUFLEN, fd)) { - break; // end of file - } - line = linebuf; - } - - if (diffstyle == DIFF_NONE) { - // Determine diff style. - // ed like diff looks like this: - // {first}[,{last}]c{first}[,{last}] - // {first}a{first}[,{last}] - // {first}[,{last}]d{first} - // - // unified diff looks like this: - // --- file1 2018-03-20 13:23:35.783153140 +0100 - // +++ file2 2018-03-20 13:23:41.183156066 +0100 - // @@ -1,3 +1,5 @@ - if (isdigit(*line)) { - diffstyle = DIFF_ED; - } else if ((STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else { - // Format not recognized yet, skip over this line. Cygwin diff - // may put a warning at the start of the file. - continue; - } + // Format not recognized yet, skip over this line. Cygwin diff + // may put a warning at the start of the file. + continue; } + } - if (diffstyle == DIFF_ED) { - if (!isdigit(*line)) { - continue; // not the start of a diff block - } - if (parse_diff_ed((char_u *)line, hunk) == FAIL) { - continue; - } - } else { - assert(diffstyle == DIFF_UNIFIED); - if (STRNCMP(line, "@@ ", 3) != 0) { - continue; // not the start of a diff block - } - if (parse_diff_unified((char_u *)line, hunk) == FAIL) { - continue; - } + if (*diffstyle == DIFF_ED) { + if (!isdigit(*line)) { + continue; // not the start of a diff block + } + if (parse_diff_ed(line, hunk) == FAIL) { + continue; + } + } else { + assert(*diffstyle == DIFF_UNIFIED); + if (STRNCMP(line, "@@ ", 3) != 0) { + continue; // not the start of a diff block + } + if (parse_diff_unified(line, hunk) == FAIL) { + continue; } } - // Go over blocks before the change, for which orig and new are equal. - // Copy blocks from orig to new. - while (dp != NULL - && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { - if (notset) { - diff_copy_entry(dprev, dp, idx_orig, idx_new); - } - dprev = dp; - dp = dp->df_next; - notset = true; + // Successfully parsed diff output, can return + return false; + } +} + +static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_new, diffhunk_T *hunk, + bool *notsetp) +{ + diff_T *dp = *dpp; + diff_T *dprev = *dprevp; + + // Go over blocks before the change, for which orig and new are equal. + // Copy blocks from orig to new. + while (dp != NULL + && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + if (*notsetp) { + diff_copy_entry(dprev, dp, idx_orig, idx_new); } + dprev = dp; + dp = dp->df_next; + *notsetp = true; + } - if ((dp != NULL) - && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) - && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { - // New block overlaps with existing block(s). - // First find last block that overlaps. - for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { - if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { - break; - } + if ((dp != NULL) + && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) + && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { + // New block overlaps with existing block(s). + // First find last block that overlaps. + diff_T *dpl; + for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { + if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { + break; } + } - // If the newly found block starts before the old one, set the - // start back a number of lines. - off = dp->df_lnum[idx_orig] - hunk->lnum_orig; + // If the newly found block starts before the old one, set the + // start back a number of lines. + linenr_T off = dp->df_lnum[idx_orig] - hunk->lnum_orig; - if (off > 0) { - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_lnum[i] -= off; - } - } - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; - } else if (notset) { - // new block inside existing one, adjust new block - dp->df_lnum[idx_new] = hunk->lnum_new + off; - dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; - } else { - // second overlap of new block with existing block - dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig - + dpl->df_lnum[idx_orig] + - dpl->df_count[idx_orig] - - (dp->df_lnum[idx_orig] + - dp->df_count[idx_orig]); - } - - // Adjust the size of the block to include all the lines to the - // end of the existing block or the new diff, whatever ends last. - off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) - - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); - - if (off < 0) { - // new change ends in existing block, adjust the end if not - // done already - if (notset) { - dp->df_count[idx_new] += -off; + if (off > 0) { + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_lnum[i] -= off; } - off = 0; } + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + } else if (*notsetp) { + // new block inside existing one, adjust new block + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; + } else { + // second overlap of new block with existing block + dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig + + dpl->df_lnum[idx_orig] + + dpl->df_count[idx_orig] + - (dp->df_lnum[idx_orig] + + dp->df_count[idx_orig]); + } + + // Adjust the size of the block to include all the lines to the + // end of the existing block or the new diff, whatever ends last. + off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) + - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); + + if (off < 0) { + // new change ends in existing block, adjust the end if not + // done already + if (*notsetp) { + dp->df_count[idx_new] += -off; + } + off = 0; + } - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] - - dp->df_lnum[i] + off; - } + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] + - dp->df_lnum[i] + off; } + } - // Delete the diff blocks that have been merged into one. - dn = dp->df_next; - dp->df_next = dpl->df_next; + // Delete the diff blocks that have been merged into one. + diff_T *dn = dp->df_next; + dp->df_next = dpl->df_next; - while (dn != dp->df_next) { - dpl = dn->df_next; - xfree(dn); - dn = dpl; + while (dn != dp->df_next) { + dpl = dn->df_next; + xfree(dn); + dn = dpl; + } + } else { + // Allocate a new diffblock. + dp = diff_alloc_new(curtab, dprev, dp); + + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + + // Set values for other buffers, these must be equal to the + // original buffer, otherwise there would have been a change + // already. + for (int i = idx_orig + 1; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + diff_copy_entry(dprev, dp, idx_orig, i); } - } else { - // Allocate a new diffblock. - dp = diff_alloc_new(curtab, dprev, dp); + } + } + *notsetp = false; // "*dp" has been set + *dpp = dp; + *dprevp = dprev; +} - dp->df_lnum[idx_orig] = hunk->lnum_orig; - dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; +/// Read the diff output and add each entry to the diff list. +/// +/// @param idx_orig idx of original file +/// @param idx_new idx of new file +/// @dout diff output +static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +{ + FILE *fd = NULL; + int line_idx = 0; + diff_T *dprev = NULL; + diff_T *dp = curtab->tp_first_diff; + diffout_T *dout = &dio->dio_diff; + bool notset = true; // block "*dp" not set yet + diffstyle_T diffstyle = DIFF_NONE; - // Set values for other buffers, these must be equal to the - // original buffer, otherwise there would have been a change - // already. - for (i = idx_orig + 1; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - diff_copy_entry(dprev, dp, idx_orig, i); - } - } + if (!dio->dio_internal) { + fd = os_fopen(dout->dout_fname, "r"); + if (fd == NULL) { + emsg(_("E98: Cannot read diff output")); + return; + } + } + + for (;;) { + diffhunk_T hunk = { 0 }; + bool eof = dio->dio_internal + ? extract_hunk_internal(dout, &hunk, &line_idx) + : extract_hunk(fd, &hunk, &diffstyle); + + if (eof) { + break; } - notset = false; // "*dp" has been set + + process_hunk(&dp, &dprev, idx_orig, idx_new, &hunk, ¬set); } -// for remaining diff blocks orig and new are equal + // for remaining diff blocks orig and new are equal while (dp != NULL) { if (notset) { diff_copy_entry(dprev, dp, idx_orig, idx_new); @@ -1739,10 +1753,6 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) notset = true; } - if (!dio->dio_internal) { - xfree(hunk); - } - if (fd != NULL) { fclose(fd); } @@ -1782,6 +1792,294 @@ void diff_clear(tabpage_T *tp) tp->tp_first_diff = NULL; } +/// +/// return true if the options are set to use diff linematch +/// +bool diff_linematch(diff_T *dp) +{ + if (!(diff_flags & DIFF_LINEMATCH)) { + return false; + } + // are there more than three diff buffers? + int tsize = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // for the rare case (bug?) that the count of a diff block is negative, do + // not run the algorithm because this will try to allocate a negative + // amount of space and crash + if (dp->df_count[i] < 0) { + return false; + } + tsize += dp->df_count[i]; + } + } + // avoid allocating a huge array because it will lag + return tsize <= linematch_lines; +} + +static int get_max_diff_length(const diff_T *dp) +{ + int maxlength = 0; + for (int k = 0; k < DB_COUNT; k++) { + if (curtab->tp_diffbuf[k] != NULL) { + if (dp->df_count[k] > maxlength) { + maxlength = dp->df_count[k]; + } + } + } + return maxlength; +} + +static void find_top_diff_block(diff_T **thistopdiff, diff_T **nextblockblock, int fromidx, + int topline) +{ + diff_T *topdiff = NULL; + diff_T *localtopdiff = NULL; + int topdiffchange = 0; + + for (topdiff = curtab->tp_first_diff; topdiff != NULL; topdiff = topdiff->df_next) { + // set the top of the current overlapping diff block set as we + // iterate through all of the sets of overlapping diff blocks + if (!localtopdiff || topdiffchange) { + localtopdiff = topdiff; + topdiffchange = 0; + } + + // check if the fromwin topline is matched by the current diff. if so, set it to the top of the diff block + if (topline >= topdiff->df_lnum[fromidx] && topline <= + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])) { + // this line is inside the current diff block, so we will save the + // top block of the set of blocks to refer to later + if ((*thistopdiff) == NULL) { + (*thistopdiff) = localtopdiff; + } + } + + // check if the next set of overlapping diff blocks is next + if (!(topdiff->df_next && (topdiff->df_next->df_lnum[fromidx] == + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])))) { + // mark that the next diff block is belongs to a different set of + // overlapping diff blocks + topdiffchange = 1; + + // if we already have found that the line number is inside a diff block, + // set the marker of the next block and finish the iteration + if (*thistopdiff) { + (*nextblockblock) = topdiff->df_next; + break; + } + } + } +} + +static void count_filler_lines_and_topline(int *curlinenum_to, int *linesfiller, + const diff_T *thistopdiff, const int toidx, + int virtual_lines_passed) +{ + const diff_T *curdif = thistopdiff; + int ch_virtual_lines = 0; + int isfiller = 0; + while (virtual_lines_passed) { + if (ch_virtual_lines) { + virtual_lines_passed--; + ch_virtual_lines--; + if (!isfiller) { + (*curlinenum_to)++; + } else { + (*linesfiller)++; + } + } else { + (*linesfiller) = 0; + ch_virtual_lines = get_max_diff_length(curdif); + isfiller = (curdif->df_count[toidx] ? 0 : 1); + if (isfiller) { + while (curdif && curdif->df_next && curdif->df_lnum[toidx] == + curdif->df_next->df_lnum[toidx] + && curdif->df_next->df_count[toidx] == 0) { + curdif = curdif->df_next; + ch_virtual_lines += get_max_diff_length(curdif); + } + } + if (curdif) { + curdif = curdif->df_next; + } + } + } +} + +static void calculate_topfill_and_topline(const int fromidx, const int toidx, const + int from_topline, const int from_topfill, int *topfill, + linenr_T *topline) +{ + // 1. find the position from the top of the diff block, and the start + // of the next diff block + diff_T *thistopdiff = NULL; + diff_T *nextblockblock = NULL; + int virtual_lines_passed = 0; + + find_top_diff_block(&thistopdiff, &nextblockblock, fromidx, from_topline); + + // count the virtual lines that have been passed + + diff_T *curdif = thistopdiff; + while (curdif && (curdif->df_lnum[fromidx] + curdif->df_count[fromidx]) + <= from_topline) { + virtual_lines_passed += get_max_diff_length(curdif); + + curdif = curdif->df_next; + } + + if (curdif != nextblockblock) { + virtual_lines_passed += from_topline - curdif->df_lnum[fromidx]; + } + virtual_lines_passed -= from_topfill; + + // count the same amount of virtual lines in the toidx buffer + curdif = thistopdiff; + int curlinenum_to = thistopdiff->df_lnum[toidx]; + int linesfiller = 0; + count_filler_lines_and_topline(&curlinenum_to, &linesfiller, + thistopdiff, toidx, virtual_lines_passed); + + // count the number of filler lines that would normally be above this line + int maxfiller = 0; + for (diff_T *dpfillertest = thistopdiff; dpfillertest != NULL; + dpfillertest = dpfillertest->df_next) { + if (dpfillertest->df_lnum[toidx] == curlinenum_to) { + while (dpfillertest && dpfillertest->df_lnum[toidx] == curlinenum_to) { + maxfiller += dpfillertest->df_count[toidx] ? 0 : get_max_diff_length(dpfillertest); + dpfillertest = dpfillertest->df_next; + } + break; + } + } + (*topfill) = maxfiller - linesfiller; + (*topline) = curlinenum_to; +} + +static int linematched_filler_lines(diff_T *dp, int idx, linenr_T lnum, int *linestatus) +{ + int filler_lines_d1 = 0; + while (dp && dp->df_next + && lnum == (dp->df_lnum[idx] + dp->df_count[idx]) + && dp->df_next->df_lnum[idx] == lnum) { + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + dp = dp->df_next; + } + + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { + int j = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + if (dp->df_count[i]) { + j++; + } + } + // is this an added line or a changed line? + if (linestatus) { + (*linestatus) = (j == 1) ? -2 : -1; + } + } + } + return filler_lines_d1; +} + +// Apply results from the linematch algorithm and apply to 'dp' by splitting it into multiple +// adjacent diff blocks. +static void apply_linematch_results(diff_T *dp, size_t decisions_length, const int *decisions) +{ + // get the start line number here in each diff buffer, and then increment + int line_numbers[DB_COUNT]; + int outputmap[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + line_numbers[i] = dp->df_lnum[i]; + dp->df_count[i] = 0; + + // Keep track of the index of the diff buffer we are using here. + // We will use this to write the output of the algorithm to + // diff_T structs at the correct indexes + outputmap[ndiffs] = i; + ndiffs++; + } + } + + // write the diffs starting with the current diff block + diff_T *dp_s = dp; + for (size_t i = 0; i < decisions_length; i++) { + // Don't allocate on first iter since we can reuse the initial diffblock + if (i != 0 && (decisions[i - 1] != decisions[i])) { + // create new sub diff blocks to segment the original diff block which we + // further divided by running the linematch algorithm + dp_s = diff_alloc_new(curtab, dp_s, dp_s->df_next); + dp_s->is_linematched = true; + for (int j = 0; j < DB_COUNT; j++) { + if (curtab->tp_diffbuf[j] != NULL) { + dp_s->df_lnum[j] = line_numbers[j]; + dp_s->df_count[j] = 0; + } + } + } + for (size_t j = 0; j < ndiffs; j++) { + if (decisions[i] & (1 << j)) { + // will need to use the map here + dp_s->df_count[outputmap[j]]++; + line_numbers[outputmap[j]]++; + } + } + } + dp->is_linematched = true; +} + +static void run_linematch_algorithm(diff_T *dp) +{ + // define buffers for diff algorithm + mmfile_t diffbufs_mm[DB_COUNT]; + const char *diffbufs[DB_COUNT]; + int diff_length[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // write the contents of the entire buffer to + // diffbufs_mm[diffbuffers_count] + diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs], + dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1); + + // we want to get the char* to the diff buffer that was just written + // we add it to the array of char*, diffbufs + diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr; + + // keep track of the length of this diff block to pass it to the linematch + // algorithm + diff_length[ndiffs] = dp->df_count[i]; + + // increment the amount of diff buffers we are passing to the algorithm + ndiffs++; + } + } + + // we will get the output of the linematch algorithm in the format of an array + // of integers (*decisions) and the length of that array (decisions_length) + int *decisions = NULL; + const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0; + size_t decisions_length = linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite); + + for (size_t i = 0; i < ndiffs; i++) { + XFREE_CLEAR(diffbufs_mm[i].ptr); + } + + apply_linematch_results(dp, decisions_length, decisions); + + xfree(decisions); +} + /// Check diff status for line "lnum" in buffer "buf": /// /// Returns 0 for nothing special @@ -1793,11 +2091,11 @@ void diff_clear(tabpage_T *tp) /// /// @param wp /// @param lnum +/// @param[out] linestatus /// /// @return diff status. -int diff_check(win_T *wp, linenr_T lnum) +int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus) { - diff_T *dp; buf_T *buf = wp->w_buffer; if (curtab->tp_diff_invalid) { @@ -1828,6 +2126,7 @@ int diff_check(win_T *wp, linenr_T lnum) } // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) { break; @@ -1838,6 +2137,14 @@ int diff_check(win_T *wp, linenr_T lnum) return 0; } + if (!dp->is_linematched && diff_linematch(dp)) { + run_linematch_algorithm(dp); + } + + if (dp->is_linematched) { + return linematched_filler_lines(dp, idx, lnum, linestatus); + } + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { int zero = false; @@ -1892,15 +2199,16 @@ int diff_check(win_T *wp, linenr_T lnum) // Insert filler lines above the line just below the change. Will return // 0 when this buf had the max count. - linenr_T maxcount = 0; - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] > maxcount)) { - maxcount = dp->df_count[i]; - } - } + int maxcount = get_max_diff_length(dp); return maxcount - dp->df_count[idx]; } +/// See diff_check_with_linestatus +int diff_check(win_T *wp, linenr_T lnum) +{ + return diff_check_with_linestatus(wp, lnum, NULL); +} + /// Compare two entries in diff "dp" and return true if they are equal. /// /// @param dp diff @@ -1936,24 +2244,24 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) // Compare the characters at "p1" and "p2". If they are equal (possibly // ignoring case) return true and set "len" to the number of bytes. -static bool diff_equal_char(const char_u *const p1, const char_u *const p2, int *const len) +static bool diff_equal_char(const char *const p1, const char *const p2, int *const len) { - const int l = utfc_ptr2len((char *)p1); + const int l = utfc_ptr2len(p1); - if (l != utfc_ptr2len((char *)p2)) { + if (l != utfc_ptr2len(p2)) { return false; } if (l > 1) { if (STRNCMP(p1, p2, l) != 0 && (!(diff_flags & DIFF_ICASE) - || utf_fold(utf_ptr2char((char *)p1)) != utf_fold(utf_ptr2char((char *)p2)))) { + || utf_fold(utf_ptr2char(p1)) != utf_fold(utf_ptr2char(p2)))) { return false; } *len = l; } else { if ((*p1 != *p2) && (!(diff_flags & DIFF_ICASE) - || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2))) { + || TOLOWER_LOC((uint8_t)(*p1)) != TOLOWER_LOC((uint8_t)(*p2)))) { return false; } *len = 1; @@ -1980,7 +2288,7 @@ static int diff_cmp(char *s1, char *s2) } if ((diff_flags & DIFF_ICASE) && !(diff_flags & ALL_WHITE_DIFF)) { - return mb_stricmp((const char *)s1, (const char *)s2); + return mb_stricmp(s1, s2); } char *p1 = s1; @@ -1996,7 +2304,7 @@ static int diff_cmp(char *s1, char *s2) p2 = skipwhite(p2); } else { int l; - if (!diff_equal_char((char_u *)p1, (char_u *)p2, &l)) { + if (!diff_equal_char(p1, p2, &l)) { break; } p1 += l; @@ -2023,7 +2331,6 @@ void diff_set_topline(win_T *fromwin, win_T *towin) { buf_T *frombuf = fromwin->w_buffer; linenr_T lnum = fromwin->w_topline; - diff_T *dp; int fromidx = diff_buf_idx(frombuf); if (fromidx == DB_COUNT) { @@ -2038,6 +2345,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topfill = 0; // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) { break; @@ -2060,46 +2368,51 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); if (lnum >= dp->df_lnum[fromidx]) { - // Inside a change: compute filler lines. With three or more - // buffers we need to know the largest count. - linenr_T max_count = 0; - - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { - max_count = dp->df_count[i]; - } - } + if (diff_flags & DIFF_LINEMATCH) { + calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline, + fromwin->w_topfill, &towin->w_topfill, &towin->w_topline); + } else { + // Inside a change: compute filler lines. With three or more + // buffers we need to know the largest count. + linenr_T max_count = 0; - if (dp->df_count[toidx] == dp->df_count[fromidx]) { - // same number of lines: use same filler count - towin->w_topfill = fromwin->w_topfill; - } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { - if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // more lines in towin and fromwin doesn't show diff - // lines, only filler lines - if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { - // towin also only shows filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - towin->w_topfill = fromwin->w_topfill; - } else { - // towin still has some diff lines to show - towin->w_topline = dp->df_lnum[toidx] - + max_count - fromwin->w_topfill; + for (int i = 0; i < DB_COUNT; i++) { + if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { + max_count = dp->df_count[i]; } } - } else if (towin->w_topline >= dp->df_lnum[toidx] - + dp->df_count[toidx]) { - // less lines in towin and no diff lines to show: compute - // filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - if (diff_flags & DIFF_FILLER) { + if (dp->df_count[toidx] == dp->df_count[fromidx]) { + // same number of lines: use same filler count + towin->w_topfill = fromwin->w_topfill; + } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // fromwin is also out of diff lines - towin->w_topfill = fromwin->w_topfill; - } else { - // fromwin has some diff lines - towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + // more lines in towin and fromwin doesn't show diff + // lines, only filler lines + if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { + // towin also only shows filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + towin->w_topfill = fromwin->w_topfill; + } else { + // towin still has some diff lines to show + towin->w_topline = dp->df_lnum[toidx] + + max_count - fromwin->w_topfill; + } + } + } else if (towin->w_topline >= dp->df_lnum[toidx] + + dp->df_count[toidx]) { + // less lines in towin and no diff lines to show: compute + // filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + + if (diff_flags & DIFF_FILLER) { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { + // fromwin is also out of diff lines + towin->w_topfill = fromwin->w_topfill; + } else { + // fromwin has some diff lines + towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + } } } } @@ -2134,6 +2447,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) int diffopt_changed(void) { int diff_context_new = 6; + int linematch_lines_new = 0; int diff_flags_new = 0; int diff_foldcolumn_new = 2; long diff_algorithm_new = 0; @@ -2203,6 +2517,10 @@ int diffopt_changed(void) } else { return FAIL; } + } else if ((STRNCMP(p, "linematch:", 10) == 0) && ascii_isdigit(p[10])) { + p += 10; + linematch_lines_new = getdigits_int(&p, false, linematch_lines_new); + diff_flags_new |= DIFF_LINEMATCH; } if ((*p != ',') && (*p != NUL)) { @@ -2231,6 +2549,7 @@ int diffopt_changed(void) diff_flags = diff_flags_new; diff_context = diff_context_new == 0 ? 1 : diff_context_new; + linematch_lines = linematch_lines_new; diff_foldcolumn = diff_foldcolumn_new; diff_algorithm = diff_algorithm_new; @@ -2281,14 +2600,6 @@ bool diffopt_filler(void) bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char *line_new; - int si_org; - int si_new; - int ei_org; - int ei_new; - bool added = true; - int l; - // Make a copy of the line, the next ml_get() will invalidate it. char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum, false)); @@ -2306,6 +2617,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) break; } } + if (dp->is_linematched) { + while (dp && dp->df_next + && lnum == dp->df_count[idx] + dp->df_lnum[idx] + && dp->df_next->df_lnum[idx] == lnum) { + dp = dp->df_next; + } + } if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) { xfree(line_org); @@ -2313,6 +2631,12 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) return false; } + int si_org; + int si_new; + int ei_org; + int ei_new; + bool added = true; + linenr_T off = lnum - dp->df_lnum[idx]; int i; for (i = 0; i < DB_COUNT; i++) { @@ -2322,7 +2646,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) continue; } added = false; - line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false); + char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false); // Search for start of difference si_org = si_new = 0; @@ -2337,7 +2661,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) si_org = (int)(skipwhite(line_org + si_org) - line_org); si_new = (int)(skipwhite(line_new + si_new) - line_new); } else { - if (!diff_equal_char((char_u *)line_org + si_org, (char_u *)line_new + si_new, &l)) { + int l; + if (!diff_equal_char(line_org + si_org, line_new + si_new, &l)) { break; } si_org += l; @@ -2377,12 +2702,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) ei_new--; } } else { - const char_u *p1 = (char_u *)line_org + ei_org; - const char_u *p2 = (char_u *)line_new + ei_new; + const char *p1 = line_org + ei_org; + const char *p2 = line_new + ei_new; - p1 -= utf_head_off(line_org, (char *)p1); - p2 -= utf_head_off(line_new, (char *)p2); + p1 -= utf_head_off(line_org, p1); + p2 -= utf_head_off(line_new, p2); + int l; if (!diff_equal_char(p1, p2, &l)) { break; } @@ -2412,16 +2738,14 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) bool diff_infold(win_T *wp, linenr_T lnum) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { - bool other = false; - // Return if 'diff' isn't set. if (!wp->w_p_diff) { return false; } int idx = -1; - int i; - for (i = 0; i < DB_COUNT; i++) { + bool other = false; + for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == wp->w_buffer) { idx = i; } else if (curtab->tp_diffbuf[i] != NULL) { @@ -2461,13 +2785,13 @@ bool diff_infold(win_T *wp, linenr_T lnum) /// "dp" and "do" commands. void nv_diffgetput(bool put, size_t count) { - exarg_T ea; - char buf[30]; - if (bt_prompt(curbuf)) { vim_beep(BO_OPER); return; } + + exarg_T ea; + char buf[30]; if (count == 0) { ea.arg = ""; } else { @@ -2503,24 +2827,7 @@ static bool valid_diff(diff_T *diff) /// @param eap void ex_diffgetput(exarg_T *eap) { - linenr_T lnum; - linenr_T count; - linenr_T off = 0; - diff_T *dp; - diff_T *dfree; - int i; - int added; - char *p; - aco_save_T aco; - buf_T *buf; - linenr_T start_skip; - linenr_T end_skip; - linenr_T new_count; - int buf_empty; - int found_not_ma = false; int idx_other; - int idx_from; - int idx_to; // Find the current buffer in the list of diff buffers. int idx_cur = diff_buf_idx(curbuf); @@ -2530,6 +2837,7 @@ void ex_diffgetput(exarg_T *eap) } if (*eap->arg == NUL) { + int found_not_ma = false; // No argument: Find the other buffer in the list of diff buffers. for (idx_other = 0; idx_other < DB_COUNT; idx_other++) { if ((curtab->tp_diffbuf[idx_other] != curbuf) @@ -2552,7 +2860,7 @@ void ex_diffgetput(exarg_T *eap) } // Check that there isn't a third buffer in the list - for (i = idx_other + 1; i < DB_COUNT; i++) { + for (int i = idx_other + 1; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != curbuf) && (curtab->tp_diffbuf[i] != NULL) && ((eap->cmdidx != CMD_diffput) @@ -2564,11 +2872,12 @@ void ex_diffgetput(exarg_T *eap) } } else { // Buffer number or pattern given. Ignore trailing white space. - p = eap->arg + strlen(eap->arg); + char *p = eap->arg + strlen(eap->arg); while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } + int i; for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) {} if (eap->arg + i == p) { @@ -2582,7 +2891,7 @@ void ex_diffgetput(exarg_T *eap) return; } } - buf = buflist_findnr(i); + buf_T *buf = buflist_findnr(i); if (buf == NULL) { semsg(_("E102: Can't find buffer \"%s\""), eap->arg); @@ -2617,13 +2926,9 @@ void ex_diffgetput(exarg_T *eap) } } - if (eap->cmdidx == CMD_diffget) { - idx_from = idx_other; - idx_to = idx_cur; - } else { - idx_from = idx_cur; - idx_to = idx_other; + aco_save_T aco; + if (eap->cmdidx != CMD_diffget) { // Need to make the other buffer the current buffer to be able to make // changes in it. @@ -2631,6 +2936,9 @@ void ex_diffgetput(exarg_T *eap) aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); } + const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur; + const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other; + // May give the warning for a changed buffer here, which can trigger the // FileChangedRO autocommand, which may do nasty things and mess // everything up. @@ -2642,26 +2950,83 @@ void ex_diffgetput(exarg_T *eap) } } + diffgetput(eap->addr_count, idx_cur, idx_from, idx_to, eap->line1, eap->line2); + + // restore curwin/curbuf and a few other things + if (eap->cmdidx != CMD_diffget) { + // Syncing undo only works for the current buffer, but we change + // another buffer. Sync undo if the command was typed. This isn't + // 100% right when ":diffput" is used in a function or mapping. + if (KeyTyped) { + u_sync(false); + } + aucmd_restbuf(&aco); + } + +theend: + diff_busy = false; + + if (diff_need_update) { + ex_diffupdate(NULL); + } + + // Check that the cursor is on a valid character and update its + // position. When there were filler lines the topline has become + // invalid. + check_cursor(); + changed_line_abv_curs(); + + if (diff_need_update) { + // redraw already done by ex_diffupdate() + diff_need_update = false; + } else { + // Also need to redraw the other buffers. + diff_redraw(false); + apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); + } +} + +/// Apply diffget/diffput to buffers and diffblocks +/// +/// @param idx_cur index of "curbuf" before aucmd_prepbuf() in the list of diff buffers +/// @param idx_from index of the buffer to read from in the list of diff buffers +/// @param idx_to index of the buffer to modify in the list of diff buffers +static void diffgetput(const int addr_count, const int idx_cur, const int idx_from, + const int idx_to, const linenr_T line1, const linenr_T line2) +{ + linenr_T off = 0; diff_T *dprev = NULL; - for (dp = curtab->tp_first_diff; dp != NULL;) { - if (dp->df_lnum[idx_cur] > eap->line2 + off) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL;) { + if (!addr_count) { + // handle the case with adjacent diff blocks + while (dp->is_linematched + && dp->df_next + && dp->df_next->df_lnum[idx_cur] == dp->df_lnum[idx_cur] + dp->df_count[idx_cur] + && dp->df_next->df_lnum[idx_cur] == line1 + off + 1) { + dprev = dp; + dp = dp->df_next; + } + } + + if (dp->df_lnum[idx_cur] > line2 + off) { // past the range that was specified break; } - dfree = NULL; - lnum = dp->df_lnum[idx_to]; - count = dp->df_count[idx_to]; + diff_T dfree = { 0 }; + bool did_free = false; + linenr_T lnum = dp->df_lnum[idx_to]; + linenr_T count = dp->df_count[idx_to]; - if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off) + if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > line1 + off) && (u_save(lnum - 1, lnum + count) != FAIL)) { // Inside the specified range and saving for undo worked. - start_skip = 0; - end_skip = 0; + linenr_T start_skip = 0; + linenr_T end_skip = 0; - if (eap->addr_count > 0) { + if (addr_count > 0) { // A range was specified: check if lines need to be skipped. - start_skip = eap->line1 + off - dp->df_lnum[idx_cur]; + start_skip = line1 + off - dp->df_lnum[idx_cur]; if (start_skip > 0) { // range starts below start of current diff block if (start_skip > count) { @@ -2676,13 +3041,13 @@ void ex_diffgetput(exarg_T *eap) } end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1 - - (eap->line2 + off); + - (line2 + off); if (end_skip > 0) { // range ends above end of current/from diff block if (idx_cur == idx_from) { // :diffput - i = dp->df_count[idx_cur] - start_skip - end_skip; + int i = dp->df_count[idx_cur] - start_skip - end_skip; if (count > i) { count = i; @@ -2701,10 +3066,10 @@ void ex_diffgetput(exarg_T *eap) } } - buf_empty = buf_is_empty(curbuf); - added = 0; + bool buf_empty = buf_is_empty(curbuf); + int added = 0; - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { // remember deleting the last line of the buffer buf_empty = curbuf->b_ml.ml_line_count == 1; if (ml_delete(lnum, false) == OK) { @@ -2712,12 +3077,12 @@ void ex_diffgetput(exarg_T *eap) } } - for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { + for (int i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { linenr_T nr = dp->df_lnum[idx_from] + start_skip + i; if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; } - p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); + char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); ml_append(lnum + i - 1, p, 0, false); xfree(p); added++; @@ -2728,12 +3093,13 @@ void ex_diffgetput(exarg_T *eap) ml_delete((linenr_T)2, false); } } - new_count = dp->df_count[idx_to] + added; + linenr_T new_count = dp->df_count[idx_to] + added; dp->df_count[idx_to] = new_count; if ((start_skip == 0) && (end_skip == 0)) { // Check if there are any other buffers and if the diff is // equal in them. + int i; for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx_from) @@ -2745,14 +3111,9 @@ void ex_diffgetput(exarg_T *eap) if (i == DB_COUNT) { // delete the diff entry, the buffers are now equal here - dfree = dp; - dp = dp->df_next; - - if (dprev == NULL) { - curtab->tp_first_diff = dp; - } else { - dprev->df_next = dp; - } + dfree = *dp; + did_free = true; + dp = diff_free(curtab, dprev, dp); } } @@ -2771,10 +3132,9 @@ void ex_diffgetput(exarg_T *eap) } changed_lines(lnum, 0, lnum + count, added, true); - if (dfree != NULL) { + if (did_free) { // Diff is deleted, update folds in other windows. - diff_fold_update(dfree, idx_to); - xfree(dfree); + diff_fold_update(&dfree, idx_to); } // mark_adjust() may have made "dp" invalid. We don't know where @@ -2783,7 +3143,7 @@ void ex_diffgetput(exarg_T *eap) break; } - if (dfree == NULL) { + if (!did_free) { // mark_adjust() may have changed the count in a wrong way dp->df_count[idx_to] = new_count; } @@ -2795,43 +3155,11 @@ void ex_diffgetput(exarg_T *eap) } // If before the range or not deleted, go to next diff. - if (dfree == NULL) { + if (!did_free) { dprev = dp; dp = dp->df_next; } } - - // restore curwin/curbuf and a few other things - if (eap->cmdidx != CMD_diffget) { - // Syncing undo only works for the current buffer, but we change - // another buffer. Sync undo if the command was typed. This isn't - // 100% right when ":diffput" is used in a function or mapping. - if (KeyTyped) { - u_sync(false); - } - aucmd_restbuf(&aco); - } - -theend: - diff_busy = false; - if (diff_need_update) { - ex_diffupdate(NULL); - } - - // Check that the cursor is on a valid character and update its - // position. When there were filler lines the topline has become - // invalid. - check_cursor(); - changed_line_abv_curs(); - - if (diff_need_update) { - // redraw already done by ex_diffupdate() - diff_need_update = false; - } else { - // Also need to redraw the other buffers. - diff_redraw(false); - apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); - } } /// Update folds for all diff buffers for entry "dp". @@ -3055,7 +3383,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) /// Handle an ED style diff line. /// Return FAIL if the line does not contain diff info. /// -static int parse_diff_ed(char_u *line, diffhunk_T *hunk) +static int parse_diff_ed(char *line, diffhunk_T *hunk) { long l1, l2; @@ -3063,7 +3391,7 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) // change: {first}[,{last}]c{first}[,{last}] // append: {first}a{first}[,{last}] // delete: {first}[,{last}]d{first} - char *p = (char *)line; + char *p = line; linenr_T f1 = getdigits_int32(&p, true, 0); if (*p == ',') { p++; @@ -3107,11 +3435,11 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) /// Parses unified diff with zero(!) context lines. /// Return FAIL if there is no diff information in "line". /// -static int parse_diff_unified(char_u *line, diffhunk_T *hunk) +static int parse_diff_unified(char *line, diffhunk_T *hunk) { // Parse unified diff hunk header: // @@ -oldline,oldcount +newline,newcount @@ - char *p = (char *)line; + char *p = line; if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') { long oldcount; long newline; @@ -3163,13 +3491,11 @@ static int parse_diff_unified(char_u *line, diffhunk_T *hunk) static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv) { diffout_T *dout = (diffout_T *)priv; - diffhunk_T *p = xmalloc(sizeof(*p)); - - ga_grow(&dout->dout_ga, 1); - p->lnum_orig = (linenr_T)start_a + 1; - p->count_orig = count_a; - p->lnum_new = (linenr_T)start_b + 1; - p->count_new = count_b; - ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + GA_APPEND(diffhunk_T, &(dout->dout_ga), ((diffhunk_T){ + .lnum_orig = (linenr_T)start_a + 1, + .count_orig = count_a, + .lnum_new = (linenr_T)start_b + 1, + .count_new = count_b, + })); return 0; } diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 1267d49ad1..c4a36e783f 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -52,7 +52,6 @@ static garray_T user_digraphs = { 0, 0, (int)sizeof(digr_T), 10, NULL }; /// Note: Characters marked with XX are not included literally, because some /// compilers cannot handle them (Amiga SAS/C is the most picky one). static digr_T digraphdefault[] = - // digraphs for Unicode from RFC1345 // (also work for ISO-8859-1 aka latin1) { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 6735551dc7..cb7d85a467 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -804,9 +804,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int bg_attr = win_bg_attr(wp); - filler_lines = diff_check(wp, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { if (diff_find_change(wp, lnum, &change_start, &change_end)) { diff_hlf = HLF_ADD; // added line } else if (change_start == 0) { @@ -817,7 +818,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { diff_hlf = HLF_ADD; // added line } - filler_lines = 0; + if (linestatus == 0) { + filler_lines = 0; + } area_highlighting = true; } VirtLines virt_lines = KV_INITIAL_VALUE; @@ -2212,7 +2215,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // flag to indicate whether prevcol equals startcol of search_hl or // one of the matches bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, - (long)(ptr - line) - 1); + (long)(ptr - line) - 1); // NOLINT(google-readability-casting) // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last @@ -2248,7 +2251,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among // 'search_hl' and the match list. - get_search_match_hl(wp, &screen_search_hl, (long)(ptr - line), &char_attr); + get_search_match_hl(wp, + &screen_search_hl, + (long)(ptr - line), // NOLINT(google-readability-casting) + &char_attr); } int eol_attr = char_attr; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index a1582eac53..adf52ef6e4 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -125,6 +125,8 @@ void conceal_check_cursor_line(void) /// default_grid.Columns to access items in default_grid.chars[]. Use Rows /// and Columns for positioning text etc. where the final size of the screen is /// needed. +/// +/// @return whether resizing has been done bool default_grid_alloc(void) { static bool resizing = false; @@ -264,17 +266,28 @@ void screen_resize(int width, int height) p_lines = Rows; p_columns = Columns; - // was invoked recursively from a VimResized autocmd, handled as a loop below - if (resizing_autocmd) { - return; - } + ui_call_grid_resize(1, width, height); int retry_count = 0; resizing_autocmd = true; - bool retry_resize = true; - while (retry_resize) { - retry_resize = default_grid_alloc(); + // In rare cases, autocommands may have altered Rows or Columns, + // so retry to check if we need to allocate the screen again. + while (default_grid_alloc()) { + // win_new_screensize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } + + RedrawingDisabled++; + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + RedrawingDisabled--; // Do not apply autocommands more than 3 times to avoid an endless loop // in case applying autocommands always changes Rows or Columns. @@ -282,33 +295,10 @@ void screen_resize(int width, int height) break; } - if (retry_resize) { - // In rare cases, autocommands may have altered Rows or Columns, - // retry to check if we need to allocate the screen again. - apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); - } + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); } resizing_autocmd = false; - - ui_call_grid_resize(1, width, height); - - // win_new_screensize will recompute floats position, but tell the - // compositor to not redraw them yet - ui_comp_set_screen_valid(false); - if (msg_grid.chars) { - msg_grid_invalid = true; - } - - // Note that the window sizes are updated before reallocating the arrays, - // thus we must not redraw here! - RedrawingDisabled++; - - win_new_screensize(); // fit the windows in the new sized screen - - comp_col(); // recompute columns for shown command and ruler - - RedrawingDisabled--; redraw_all_later(UPD_CLEAR); if (starting != NO_SCREEN) { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index e37f967a2c..c99449e0f3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -439,8 +439,7 @@ static int insert_check(VimState *state) s->mincol = curwin->w_wcol; validate_cursor_col(); - if ( - curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), + 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 @@ -4148,7 +4147,7 @@ static void ins_mousescroll(int dir) if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); - } else { + } else if (p_mousescroll_vert > 0) { scroll_redraw(dir, p_mousescroll_vert); } } else { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 245ad8d9d8..1200ba20ba 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -65,6 +65,7 @@ static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); static char *e_write2 = N_("E80: Error while writing: %s"); static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); +static char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s"); static char * const namespace_char = "abglstvw"; @@ -132,8 +133,7 @@ static struct vimvar { char *vv_name; ///< Name of the variable, without v:. TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. -} vimvars[] = -{ +} vimvars[] = { // VV_ tails differing from upcased string literals: // VV_CC_FROM "charconvert_from" // VV_CC_TO "charconvert_to" @@ -489,7 +489,7 @@ 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(const char *name, char *value) +void set_internal_string_var(const char *name, char *value) // NOLINT(readability-non-const-parameter) FUNC_ATTR_NONNULL_ARG(1) { typval_T tv = { @@ -545,7 +545,7 @@ int var_redir_start(char *name, int append) // check if we can write to the variable: set it to or append an empty // string - int save_emsg = did_emsg; + const int called_emsg_before = called_emsg; did_emsg = false; typval_T tv; tv.v_type = VAR_STRING; @@ -556,9 +556,7 @@ int var_redir_start(char *name, int append) set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); - int err = did_emsg; - did_emsg |= save_emsg; - if (err) { + if (called_emsg > called_emsg_before) { redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); return FAIL; @@ -1443,12 +1441,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const } wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE && tv_is_func(*rettv) - && !var_check_func_name((const char *)key, lp->ll_di == NULL)) + && var_wrong_func_name((const char *)key, lp->ll_di == NULL)) || !valid_varname((const char *)key)); if (len != -1) { key[len] = prevval; } if (wrong) { + tv_clear(&var1); return NULL; } } @@ -2184,7 +2183,7 @@ char *get_user_var_name(expand_T *xp, int idx) /// Does not use 'cpo' and always uses 'magic'. /// /// @return true if "pat" matches "text". -int pattern_match(char *pat, char *text, bool ic) +int pattern_match(const char *pat, const char *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -2192,7 +2191,7 @@ int pattern_match(char *pat, char *text, bool ic) // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; p_cpo = empty_option; - regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; matches = vim_regexec_nl(®match, (char_u *)text, (colnr_T)0); @@ -2910,6 +2909,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) const char *start_leader, *end_leader; int ret = OK; char *alias; + static int recurse = 0; // Initialise variable so that tv_clear() can't mistake this for a // string and free a string that isn't there. @@ -2922,6 +2922,20 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) } end_leader = *arg; + // Limit recursion to 1000 levels. At least at 10000 we run out of stack + // and crash. With MSVC the stack is smaller. + if (recurse == +#ifdef _MSC_VER + 300 +#else + 1000 +#endif + ) { + semsg(_(e_expression_too_recursive_str), *arg); + return FAIL; + } + recurse++; + switch (**arg) { // Number constant. case '0': @@ -3035,7 +3049,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '#': if ((*arg)[1] == '{') { (*arg)++; - ret = dict_get_tv(arg, rettv, evaluate, true); + ret = eval_dict(arg, rettv, evaluate, true); } else { ret = NOTDONE; } @@ -3046,7 +3060,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { - ret = dict_get_tv(arg, rettv, evaluate, false); + ret = eval_dict(arg, rettv, evaluate, false); } break; @@ -3126,6 +3140,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (ret == OK && evaluate && end_leader > start_leader) { ret = eval7_leader(rettv, (char *)start_leader, &end_leader); } + + recurse--; return ret; } @@ -4225,11 +4241,11 @@ bool garbage_collect(bool testing) // history items (ShaDa additional elements) if (p_hi) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { const void *iter = NULL; do { histentry_T hist; - iter = hist_iter(iter, i, false, &hist); + iter = hist_iter(iter, (uint8_t)i, false, &hist); if (hist.hisstr != NULL) { ABORTING(set_ref_list)(hist.additional_elements, copyID); } @@ -4577,7 +4593,7 @@ static int get_literal_key(char **arg, typval_T *tv) /// "literal" is true for #{key: val} /// /// @return OK or FAIL. Returns NOTDONE for {expr}. -static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) +static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) { typval_T tv; char *key = NULL; @@ -4785,20 +4801,20 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) int save_did_emsg; int idx = 0; + // Always return the first argument, also on failure. + tv_copy(&argvars[0], rettv); + if (argvars[0].v_type == VAR_BLOB) { - tv_copy(&argvars[0], rettv); if ((b = argvars[0].vval.v_blob) == NULL) { return; } } else if (argvars[0].v_type == VAR_LIST) { - tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL || (!map && var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { - tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL || (!map && var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; @@ -5184,6 +5200,7 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) if (tv->v_type == VAR_STRING && tv->vval.v_string != NULL && tv->vval.v_string[0] == '$' + && tv->vval.v_string[1] == NUL && buf != NULL) { return buf->b_ml.ml_line_count; } @@ -5563,6 +5580,13 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T const char *line = NULL; if (lines->v_type == VAR_LIST) { l = lines->vval.v_list; + if (l == NULL || tv_list_len(l) == 0) { + // set proper return code + if (lnum > curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = 1; // FAIL + } + goto done; + } li = tv_list_first(l); } else { line = tv_get_string_chk(lines); @@ -5633,6 +5657,7 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T update_topline(curwin); } +done: if (!is_curbuf) { curbuf = curbuf_save; curwin = curwin_save; @@ -8311,7 +8336,7 @@ repeat: /// "flags" can be "g" to do a global substitute. /// /// @return an allocated string, NULL for error. -char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags) +char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char *flags) { int sublen; regmatch_T regmatch; @@ -8855,7 +8880,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic) case EXPR_MATCH: case EXPR_NOMATCH: - n1 = pattern_match((char *)s2, (char *)s1, ic); + n1 = pattern_match(s2, s1, ic); if (type == EXPR_NOMATCH) { n1 = !n1; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index cc26bbf1a8..8bfa6797d0 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -38,7 +38,7 @@ return { assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, - assert_fails={args={1, 3}, base=1}, + assert_fails={args={1, 5}, base=1}, assert_false={args={1, 2}, base=1}, assert_inrange={args={3, 4}, base=3}, assert_match={args={2, 3}, base=2}, @@ -118,7 +118,7 @@ return { exists={args=1, base=1}, exp={args=1, base=1, float_func="exp"}, expand={args={1, 3}, base=1}, - expandcmd={args=1, base=1}, + expandcmd={args={1, 2}, base=1}, extend={args={2, 3}, base=1}, feedkeys={args={1, 2}, base=1}, file_readable={args=1, base=1, func='f_filereadable'}, -- obsolete @@ -185,6 +185,7 @@ return { gettabvar={args={2, 3}, base=1}, gettabwinvar={args={3, 4}, base=1}, gettagstack={args={0, 1}, base=1}, + gettext={args=1, base=1}, getwininfo={args={0, 1}, base=1}, getwinpos={args={0, 1}, base=1}, getwinposx={}, @@ -290,6 +291,7 @@ return { perleval={args=1, base=1}, rand={args={0, 1}, base=1}, range={args={1, 3}, base=1}, + readblob={args=1, base=1}, readdir={args={1, 2}, base=1}, readfile={args={1, 3}, base=1}, reduce={args={2, 3}, base=1}, @@ -374,6 +376,7 @@ return { str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, str2nr={args={1, 3}, base=1}, + strcharlen={args=1, base=1}, strcharpart={args={2, 3}, base=1}, strchars={args={1, 2}, base=1}, strdisplaywidth={args={1, 2}, base=1}, diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 8755ff48ac..c0c98e0990 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -15,9 +15,7 @@ /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_msgpack(msgpack_packer *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname); /// Convert VimL value to :echo output /// @@ -26,9 +24,7 @@ int encode_vim_to_msgpack(msgpack_packer *const packer, /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_echo(garray_T *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_echo(garray_T *packer, typval_T *tv, const char *objname); /// Structure defining state for read_from_list() typedef struct { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 5073ab8f1b..26a5c133da 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -112,6 +112,10 @@ PRAGMA_DIAG_POP static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static char e_using_number_as_bool_nr[] + = N_("E1023: Using a Number as a Bool: %d"); +static char e_cannot_resize_window_in_another_tab_page[] + = N_("E1308: Cannot resize a window in another tab page"); /// Dummy va_list for passing to vim_snprintf /// @@ -363,23 +367,34 @@ static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "append(lnum, string/list)" function static void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + const int did_emsg_before = did_emsg; const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); + } } -/// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// Set or append lines to a buffer. +static void buf_set_append_line(typval_T *argvars, typval_T *rettv, bool append) { + const int did_emsg_before = did_emsg; buf_T *const buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { rettv->vval.v_number = 1; // FAIL } else { const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); + if (did_emsg == did_emsg_before) { + set_buffer_lines(buf, lnum, append, &argvars[2], rettv); + } } } +/// "appendbufline(buf, lnum, string/list)" function +static void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_set_append_line(argvars, rettv, true); +} + /// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -893,7 +908,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) countcc = (int)tv_get_number(&argvars[2]); } if (countcc < 0 || countcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), countcc); return; } @@ -1345,10 +1360,10 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = (int)tv_get_number_chk(&argvars[1], NULL); + noref = (int)tv_get_bool_chk(&argvars[1], NULL); } if (noref < 0 || noref > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), noref); } else { var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() @@ -1468,9 +1483,10 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fp /// "deletebufline()" function static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + const int did_emsg_before = did_emsg; + rettv->vval.v_number = 1; // FAIL by default buf_T *const buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL return; } const bool is_curbuf = buf == curbuf; @@ -1478,6 +1494,9 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fpt linenr_T last; const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } if (argvars[2].v_type != VAR_UNKNOWN) { last = tv_get_lnum_buf(&argvars[2], buf); } else { @@ -1486,7 +1505,6 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fpt if (buf->b_ml.ml_mfp == NULL || first < 1 || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL return; } @@ -1539,6 +1557,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fpt curwin = curwin_save; VIsual_active = save_VIsual_active; } + rettv->vval.v_number = 0; // OK } /// "did_filetype()" function @@ -1571,9 +1590,10 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) || changedtick != buf_get_changedtick(curbuf) || fnum != curbuf->b_fnum) { // New line, buffer, change: need to get the values. - int filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { change_start = MAXCOL; change_end = -1; if (diff_find_change(curwin, lnum, &change_start, &change_end)) { @@ -2038,6 +2058,12 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *errormsg = NULL; + bool emsgoff = true; + + if (argvars[1].v_type == VAR_DICT + && tv_dict_get_bool(argvars[1].vval.v_dict, "errmsg", kBoolVarFalse)) { + emsgoff = false; + } rettv->v_type = VAR_STRING; char *cmdstr = xstrdup(tv_get_string(&argvars[0])); @@ -2051,9 +2077,17 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) }; eap.argt |= EX_NOSPC; - emsg_off++; - expand_filename(&eap, &cmdstr, &errormsg); - emsg_off--; + if (emsgoff) { + emsg_off++; + } + if (expand_filename(&eap, &cmdstr, &errormsg) == FAIL) { + if (!emsgoff && errormsg != NULL && *errormsg != NUL) { + emsg(errormsg); + } + } + if (emsgoff) { + emsg_off--; + } rettv->vval.v_string = cmdstr; } @@ -2572,9 +2606,12 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli /// "getbufline()" function static void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + const int did_emsg_before = did_emsg; buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN ? lnum : tv_get_lnum_buf(&argvars[2], buf)); @@ -3502,6 +3539,19 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); } +/// "gettext()" function +static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_STRING + || argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL) { + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); + } +} + /// "has()" function static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5857,17 +5907,17 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ga_clear_strings(&ga); } -/// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "readfile()" or "readblob()" function +static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) { bool binary = false; - bool blob = false; + bool blob = always_blob; FILE *fd; char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); char *prev = NULL; // previously read bytes, if any - long prevlen = 0; // length of data in prev - long prevsize = 0; // size of prev buffer + ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevsize = 0; // size of prev buffer long maxline = MAXLNUM; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -5921,7 +5971,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); p++) { - if (*p == '\n' || readlen <= 0) { + if (readlen <= 0 || *p == '\n') { char *s = NULL; size_t len = (size_t)(p - start); @@ -5996,8 +6046,8 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int adjust_prevlen = 0; if (dest < buf) { // -V782 - adjust_prevlen = (int)(buf - dest); // -V782 // adjust_prevlen must be 1 or 2. + adjust_prevlen = (int)(buf - dest); // -V782 dest = buf; } if (readlen > p - buf + 1) { @@ -6022,17 +6072,17 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // small, to avoid repeatedly 'allocing' large and // 'reallocing' small. if (prevsize == 0) { - prevsize = (long)(p - start); + prevsize = p - start; } else { - long grow50pc = (prevsize * 3) / 2; - long growmin = (long)((p - start) * 2 + prevlen); + ptrdiff_t grow50pc = (prevsize * 3) / 2; + ptrdiff_t growmin = (p - start) * 2 + prevlen; prevsize = grow50pc > growmin ? grow50pc : growmin; } prev = xrealloc(prev, (size_t)prevsize); } // Add the line part to end of "prev". memmove(prev + prevlen, start, (size_t)(p - start)); - prevlen += (long)(p - start); + prevlen += p - start; } } // while @@ -6040,6 +6090,18 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) fclose(fd); } +/// "readblob()" function +static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, true); +} + +/// "readfile()" function +static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, false); +} + /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -7473,16 +7535,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "setbufline()" function static void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - linenr_T lnum; - buf_T *buf; - - buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, false, &argvars[2], rettv); - } + buf_set_append_line(argvars, rettv, false); } /// Set the cursor or mark position. @@ -7611,8 +7664,11 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "setline()" function static void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + const int did_emsg_before = did_emsg; linenr_T lnum = tv_get_lnum(&argvars[0]); - set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); + } } /// "setpos()" function @@ -8120,7 +8176,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) typeerr = true; } if (argvars[2].v_type != VAR_UNKNOWN) { - keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); + keepempty = (bool)tv_get_bool_chk(&argvars[2], &typeerr); } } if (pat == NULL || *pat == NUL) { @@ -8250,7 +8306,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg(_(e_invarg)); return; } - if (argvars[2].v_type != VAR_UNKNOWN && tv_get_number(&argvars[2])) { + if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) { what |= STR2NR_QUOTE; } } @@ -8405,26 +8461,38 @@ static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } -/// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc) { const char *s = tv_get_string(&argvars[0]); - int skipcc = 0; varnumber_T len = 0; int (*func_mb_ptr2char_adv)(const char_u **pp); + func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; + while (*s != NUL) { + func_mb_ptr2char_adv((const char_u **)&s); + len++; + } + rettv->vval.v_number = len; +} + +/// "strcharlen()" function +static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + strchar_common(argvars, rettv, true); +} + +/// "strchars()" function +static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int skipcc = false; + if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = (int)tv_get_number_chk(&argvars[1], NULL); + skipcc = (int)tv_get_bool(&argvars[1]); } if (skipcc < 0 || skipcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), skipcc); } else { - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv((const char_u **)&s); - len++; - } - rettv->vval.v_number = len; + strchar_common(argvars, rettv, skipcc); } } @@ -9663,6 +9731,10 @@ static void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncDat if (wp == NULL || wp->w_floating) { return; } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } int offset = (int)tv_get_number(&argvars[1]); win_drag_vsep_line(wp, offset); @@ -9681,6 +9753,10 @@ static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncDa if (wp == NULL || wp->w_floating) { return; } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } offset = (int)tv_get_number(&argvars[1]); win_drag_status_line(wp, offset); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index a0b06aaaf4..31b73f8d48 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -43,7 +43,7 @@ static char e_string_required_for_argument_nr[] = N_("E1174: String required for argument %d"); static char e_non_empty_string_required_for_argument_nr[] - = N_("E1142: Non-empty string required for argument %d"); + = N_("E1175: Non-empty string required for argument %d"); static char e_number_required_for_argument_nr[] = N_("E1210: Number required for argument %d"); @@ -1094,7 +1094,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) tv_clear(&argv[1]); if (res == FAIL) { + // XXX: ITEM_COMPARE_FAIL is unused res = ITEM_COMPARE_FAIL; + sortinfo->item_compare_func_err = true; } else { res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); if (res > 0) { @@ -1257,7 +1259,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } else { li = TV_LIST_ITEM_NEXT(l, li); } - if (info.item_compare_func_err) { // -V547 + if (info.item_compare_func_err) { emsg(_("E882: Uniq compare function failed")); break; } @@ -1361,7 +1363,7 @@ void tv_list_reverse(list_T *const l) /// true list will not be modified. Must be initialized to false /// by the caller. void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, - const ListSorter item_compare_func, bool *errp) + const ListSorter item_compare_func, const bool *errp) FUNC_ATTR_NONNULL_ARG(3, 4) { const int len = tv_list_len(l); @@ -1839,6 +1841,7 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) di->di_key[key_len] = NUL; di->di_flags = DI_FLAGS_ALLOC; di->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv.v_type = VAR_UNKNOWN; return di; } @@ -2058,9 +2061,24 @@ int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv) varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tv_dict_get_number_def(d, key, 0); +} + +/// Get a number item from a dictionary. +/// +/// Returns "def" if the entry doesn't exist. +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Key to find in dictionary. +/// @param[in] def Default value. +/// +/// @return Dictionary item. +varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key, const int def) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ dictitem_T *const di = tv_dict_find(d, key, -1); if (di == NULL) { - return 0; + return def; } return tv_get_number(&di->di_tv); } @@ -2434,7 +2452,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action // Check the key to be valid when adding to any scope. if (d1->dv_scope == VAR_DEF_SCOPE && tv_is_func(di2->di_tv) - && !var_check_func_name((const char *)di2->di_key, di1 == NULL)) { + && var_wrong_func_name((const char *)di2->di_key, di1 == NULL)) { break; } if (!valid_varname((const char *)di2->di_key)) { @@ -2488,10 +2506,14 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool if (d1 == d2) { return true; } - if (d1 == NULL || d2 == NULL) { + if (tv_dict_len(d1) != tv_dict_len(d2)) { return false; } - if (tv_dict_len(d1) != tv_dict_len(d2)) { + if (tv_dict_len(d1) == 0) { + // empty and NULL dicts are considered equal + return true; + } + if (d1 == NULL || d2 == NULL) { return false; } @@ -3588,13 +3610,13 @@ bool tv_check_str_or_nr(const typval_T *const tv) #define FUNC_ERROR "E703: Using a Funcref as a Number" static const char *const num_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E745: Using a List as a Number"), - [VAR_DICT]=N_("E728: Using a Dictionary as a Number"), - [VAR_FLOAT]=N_("E805: Using a Float as a Number"), - [VAR_BLOB]=N_("E974: Using a Blob as a Number"), - [VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E745: Using a List as a Number"), + [VAR_DICT]= N_("E728: Using a Dictionary as a Number"), + [VAR_FLOAT]= N_("E805: Using a Float as a Number"), + [VAR_BLOB]= N_("E974: Using a Blob as a Number"), + [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"), }; #undef FUNC_ERROR @@ -3633,13 +3655,13 @@ bool tv_check_num(const typval_T *const tv) #define FUNC_ERROR "E729: using Funcref as a String" static const char *const str_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E730: using List as a String"), - [VAR_DICT]=N_("E731: using Dictionary as a String"), - [VAR_FLOAT]=((const char *)e_float_as_string), - [VAR_BLOB]=N_("E976: using Blob as a String"), - [VAR_UNKNOWN]=N_("E908: using an invalid value as a String"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E730: using List as a String"), + [VAR_DICT]= N_("E731: using Dictionary as a String"), + [VAR_FLOAT]= ((const char *)e_float_as_string), + [VAR_BLOB]= N_("E976: using Blob as a String"), + [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"), }; #undef FUNC_ERROR diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 0a4adc1f53..6757d058ef 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -43,10 +43,8 @@ static inline ListLog *list_log_new(const size_t size) return ret; } -static inline void list_log(const list_T *const l, - const listitem_T *const li1, - const listitem_T *const li2, - const char *const action) +static inline void list_log(const list_T *const l, const listitem_T *const li1, + const listitem_T *const li2, const char *const action) REAL_FATTR_ALWAYS_INLINE; /// Add new entry to log @@ -90,7 +88,7 @@ static inline void list_log(const list_T *const l, const listitem_T *const li1, #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *const l) +static inline void tv_list_ref(list_T *l) REAL_FATTR_ALWAYS_INLINE; /// Increase reference count for a given list @@ -106,7 +104,7 @@ static inline void tv_list_ref(list_T *const l) l->lv_refcount++; } -static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) +static inline void tv_list_set_ret(typval_T *tv, list_T *l) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a list as the return value. Increments the reference count. @@ -120,7 +118,7 @@ static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) tv_list_ref(l); } -static inline VarLockStatus tv_list_locked(const list_T *const l) +static inline VarLockStatus tv_list_locked(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get list lock status @@ -163,7 +161,7 @@ static inline void tv_list_set_copyid(list_T *const l, const int copyid) l->lv_copyID = copyid; } -static inline int tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list @@ -178,7 +176,7 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } -static inline int tv_list_copyid(const list_T *const l) +static inline int tv_list_copyid(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get list copyID @@ -191,7 +189,7 @@ static inline int tv_list_copyid(const list_T *const l) return l->lv_copyID; } -static inline list_T *tv_list_latest_copy(const list_T *const l) +static inline list_T *tv_list_latest_copy(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get latest list copy @@ -206,7 +204,7 @@ static inline list_T *tv_list_latest_copy(const list_T *const l) return l->lv_copylist; } -static inline int tv_list_uidx(const list_T *const l, int n) +static inline int tv_list_uidx(const list_T *l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Normalize index: that is, return either -1 or non-negative index @@ -229,7 +227,7 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } -static inline bool tv_list_has_watchers(const list_T *const l) +static inline bool tv_list_has_watchers(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check whether list has watchers @@ -244,7 +242,7 @@ static inline bool tv_list_has_watchers(const list_T *const l) return l && l->lv_watch; } -static inline listitem_T *tv_list_first(const list_T *const l) +static inline listitem_T *tv_list_first(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get first list item @@ -262,7 +260,7 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } -static inline listitem_T *tv_list_last(const list_T *const l) +static inline listitem_T *tv_list_last(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get last list item @@ -280,7 +278,7 @@ static inline listitem_T *tv_list_last(const list_T *const l) return l->lv_last; } -static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) +static inline void tv_dict_set_ret(typval_T *tv, dict_T *d) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a dictionary as the return value @@ -296,7 +294,7 @@ static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) } } -static inline long tv_dict_len(const dict_T *const d) +static inline long tv_dict_len(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a Dictionary @@ -310,7 +308,7 @@ static inline long tv_dict_len(const dict_T *const d) return (long)d->dv_hashtab.ht_used; } -static inline bool tv_dict_is_watched(const dict_T *const d) +static inline bool tv_dict_is_watched(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check if dictionary is watched @@ -323,7 +321,7 @@ static inline bool tv_dict_is_watched(const dict_T *const d) return d && !QUEUE_EMPTY(&d->watchers); } -static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) +static inline void tv_blob_set_ret(typval_T *tv, blob_T *b) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a blob as the return value. @@ -341,7 +339,7 @@ static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) } } -static inline int tv_blob_len(const blob_T *const b) +static inline int tv_blob_len(const blob_T *b) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the length of the data in the blob, in bytes. @@ -355,7 +353,7 @@ static inline int tv_blob_len(const blob_T *const b) return b->bv_ga.ga_len; } -static inline char_u tv_blob_get(const blob_T *const b, int idx) +static inline char_u tv_blob_get(const blob_T *b, int idx) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Get the byte at index `idx` in the blob. @@ -369,7 +367,7 @@ static inline char_u tv_blob_get(const blob_T *const b, int idx) return ((char_u *)b->bv_ga.ga_data)[idx]; } -static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +static inline void tv_blob_set(blob_T *b, int idx, char_u c) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Store the byte `c` at index `idx` in the blob. @@ -488,8 +486,7 @@ extern bool tv_in_free_unref_items; } \ }) -static inline bool tv_get_float_chk(const typval_T *const tv, - float_T *const ret_f) +static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Get the float value @@ -527,7 +524,7 @@ static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) return QUEUE_DATA(q, DictWatcher, node); } -static inline bool tv_is_func(const typval_T tv) +static inline bool tv_is_func(typval_T tv) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST; /// Check whether given typval_T contains a function @@ -562,4 +559,9 @@ EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.h.generated.h" #endif + +#define tv_get_bool tv_get_number +#define tv_get_bool_chk tv_get_number_chk +#define tv_dict_get_bool tv_dict_get_number_def + #endif // NVIM_EVAL_TYPVAL_H diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 33e19c531c..2f19144da3 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -71,7 +71,7 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; #define _mp_pop kv_pop #define _mp_last kv_last -static inline size_t tv_strlen(const typval_T *const tv) +static inline size_t tv_strlen(const typval_T *tv) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index fdeb52053c..4024410a04 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -48,6 +48,8 @@ static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace static char *e_funcdict = N_("E717: Dictionary entry already exists"); static char *e_funcref = N_("E718: Funcref required"); static char *e_nofunc = N_("E130: Unknown function: %s"); +static char e_no_white_space_allowed_before_str_str[] + = N_("E1068: No white space allowed before '%s': %s"); void func_init(void) { @@ -149,6 +151,15 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int emsg(_("E989: Non-default argument follows default argument")); mustend = true; } + + if (ascii_iswhite(*p) && *skipwhite(p) == ',') { + // Be tolerant when skipping + if (!skip) { + semsg(_(e_no_white_space_allowed_before_str_str), ",", p); + goto err_ret; + } + p = skipwhite(p); + } if (*p == ',') { p++; } else { @@ -449,7 +460,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex int i = 0; if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know + // Prepare for calling test_garbagecollect_now(), need to know // what variables are used on the call stack. if (funcargs.ga_itemsize == 0) { ga_init(&funcargs, (int)sizeof(typval_T *), 50); @@ -987,7 +998,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett listitem_T *li = &fc->l_listitems[ai]; *TV_LIST_ITEM_TV(li) = argvars[i]; - TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; + TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; tv_list_append(&fc->l_varlist, li); } } @@ -1193,7 +1204,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett /// For the first we only count the name stored in func_hashtab as a reference, /// using function() does not count as a reference, because the function is /// looked up by name. -static bool func_name_refcount(char_u *name) +static bool func_name_refcount(const char_u *name) { return isdigit(*name) || *name == '<'; } @@ -1335,10 +1346,10 @@ void free_all_functions(void) /// @param[in] len length of "name", or -1 for NUL terminated. /// /// @return true if "name" looks like a builtin function name: starts with a -/// lower case letter and doesn't contain AUTOLOAD_CHAR. +/// lower case letter and doesn't contain AUTOLOAD_CHAR or ':'. static bool builtin_function(const char *name, int len) { - if (!ASCII_ISLOWER(name[0])) { + if (!ASCII_ISLOWER(name[0]) || name[1] == ':') { return false; } @@ -1877,6 +1888,36 @@ theend: #define MAX_FUNC_NESTING 50 +/// List functions. +/// +/// @param regmatch When NULL, all of them. +/// Otherwise functions matching "regmatch". +static void list_functions(regmatch_T *regmatch) +{ + const size_t used = func_hashtab.ht_used; + size_t todo = used; + const hashitem_T *const ht_array = func_hashtab.ht_array; + + for (const hashitem_T *hi = ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + ufunc_T *fp = HI2UF(hi); + todo--; + if ((fp->uf_flags & FC_DEAD) == 0 + && (regmatch == NULL + ? (!message_filtered((char *)fp->uf_name) + && !func_name_refcount(fp->uf_name)) + : (!isdigit(*fp->uf_name) + && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) { + list_func_head(fp, false, false); + if (used != func_hashtab.ht_used || ht_array != func_hashtab.ht_array) { + emsg(_("E454: function list was modified")); + return; + } + } + } + } +} + /// ":function" void ex_function(exarg_T *eap) { @@ -1903,7 +1944,6 @@ void ex_function(exarg_T *eap) static int func_nr = 0; // number for nameless function int paren; hashtab_T *ht; - int todo; hashitem_T *hi; linenr_T sourcing_lnum_off; linenr_T sourcing_lnum_top; @@ -1916,19 +1956,7 @@ void ex_function(exarg_T *eap) // ":function" without argument: list functions. if (ends_excmd(*eap->arg)) { if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (message_filtered((char *)fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } + list_functions(NULL); } eap->nextcmd = check_nextcmd(eap->arg); return; @@ -1936,7 +1964,7 @@ void ex_function(exarg_T *eap) // ":function /pat": list functions matching pattern. if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', true, NULL); + p = skip_regexp(eap->arg + 1, '/', true); if (!eap->skip) { regmatch_T regmatch; @@ -1946,18 +1974,7 @@ void ex_function(exarg_T *eap) *p = c; if (regmatch.regprog != NULL) { regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, (char *)fp->uf_name, 0)) { - list_func_head(fp, false, false); - } - } - } + list_functions(®match); vim_regfree(regmatch.regprog); } } @@ -2687,6 +2704,13 @@ void ex_delfunction(exarg_T *eap) *p = NUL; } + if (isdigit(*name) && fudi.fd_dict == NULL) { + if (!eap->skip) { + semsg(_(e_invarg2), eap->arg); + } + xfree(name); + return; + } if (!eap->skip) { fp = find_func(name); } diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 4d7214205d..ef6e3f02e2 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1257,7 +1257,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, v = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && !var_check_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { return; } @@ -1445,9 +1445,9 @@ bool var_check_fixed(const int flags, const char *name, size_t name_len) /// @param[in] name Possible function/funcref name. /// @param[in] new_var True if it is a name for a variable. /// -/// @return false in case of error, true in case of success. Also gives an +/// @return false in case of success, true in case of failure. Also gives an /// error message if appropriate. -bool var_check_func_name(const char *const name, const bool new_var) +bool var_wrong_func_name(const char *const name, const bool new_var) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { // Allow for w: b: s: and t:. @@ -1455,16 +1455,16 @@ bool var_check_func_name(const char *const name, const bool new_var) && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] : name[0])) { semsg(_("E704: Funcref variable name must start with a capital: %s"), name); - return false; + return true; } // Don't allow hiding a function. When "v" is not NULL we might be // assigning another function to the same var, the type is checked // below. if (new_var && function_exists(name, false)) { semsg(_("E705: Variable name conflicts with existing function: %s"), name); - return false; + return true; } - return true; + return false; } // TODO(ZyX-I): move to eval/expressions diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index b580d3c33d..f2858f0b6d 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -16,8 +16,7 @@ typedef struct stream Stream; /// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. -typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof); +typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof); /// Type of function called when the Stream has information about a write /// request. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 781463b881..89e6d47950 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -504,9 +504,8 @@ void ex_sort(exarg_T *eap) eap->nextcmd = check_nextcmd(p); break; } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { - s = skip_regexp(p + 1, *p, true, NULL); - if (*s != *p) { - emsg(_(e_invalpat)); + s = skip_regexp_err(p + 1, *p, true); + if (s == NULL) { goto sortend; } *s = NUL; @@ -3239,7 +3238,7 @@ void sub_set_replacement(SubReplacementString sub) /// @param[in] save Save pattern to options, history /// /// @returns true if :substitute can be replaced with a join command -static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, bool save) +static bool sub_joining_lines(exarg_T *eap, char *pat, const char *sub, const char *cmd, bool save) FUNC_ATTR_NONNULL_ARG(1, 3, 4) { // TODO(vim): find a generic solution to make line-joining operations more @@ -3409,6 +3408,30 @@ static int check_regexp_delim(int c) /// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means. static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr) { +#define ADJUST_SUB_FIRSTLNUM() \ + do { \ + /* For a multi-line match, make a copy of the last matched */ \ + /* line and continue in that one. */ \ + if (nmatch > 1) { \ + sub_firstlnum += (linenr_T)nmatch - 1; \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ + /* When going beyond the last line, stop substituting. */ \ + if (sub_firstlnum <= line2) { \ + do_again = true; \ + } else { \ + subflags.do_all = false; \ + } \ + } \ + if (skip_match) { \ + /* Already hit end of the buffer, sub_firstlnum is one */ \ + /* less than what it ought to be. */ \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(""); \ + copycol = 0; \ + } \ + } while (0) + long i = 0; regmmatch_T regmatch; static subflags_T subflags = { @@ -3479,7 +3502,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T which_pat = RE_LAST; // use last used regexp delimiter = (char_u)(*cmd++); // remember delimiter character pat = cmd; // remember start of search pat - cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delimiter, p_magic, &eap->arg, NULL); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -3980,30 +4003,6 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T skip_match = true; } -#define ADJUST_SUB_FIRSTLNUM() \ - do { \ - /* For a multi-line match, make a copy of the last matched */ \ - /* line and continue in that one. */ \ - if (nmatch > 1) { \ - sub_firstlnum += (linenr_T)nmatch - 1; \ - xfree(sub_firstline); \ - sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ - /* When going beyond the last line, stop substituting. */ \ - if (sub_firstlnum <= line2) { \ - do_again = true; \ - } else { \ - subflags.do_all = false; \ - } \ - } \ - if (skip_match) { \ - /* Already hit end of the buffer, sub_firstlnum is one */ \ - /* less than what it ought to be. */ \ - xfree(sub_firstline); \ - sub_firstline = xstrdup(""); \ - copycol = 0; \ - } \ - } while (0) - // Save the line numbers for the preview buffer // NOTE: If the pattern matches a final newline, the next line will // be shown also, but should not be highlighted. Intentional for now. @@ -4536,7 +4535,7 @@ void ex_global(exarg_T *eap) delim = *cmd; // get the delimiter cmd++; // skip delimiter if there is one pat = cmd; // remember start of pattern - cmd = skip_regexp(cmd, delim, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delim, p_magic, &eap->arg, NULL); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } @@ -4849,7 +4848,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) *s = p + 1; } c = (char_u)(*p); - p = skip_regexp(p + 1, c, true, NULL); + p = skip_regexp(p + 1, c, true); if (*p != c) { return NULL; } diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 71956b2246..0015a82880 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -62,7 +62,7 @@ #define EX_FLAGS 0x200000u // allow flags after count in argument #define EX_LOCK_OK 0x1000000u // command can be executed when textlock is // set; when missing disallows editing another - // buffer when current buffer is locked + // buffer when curbuf->b_ro_locked is set #define EX_KEEPSCRIPT 0x4000000u // keep sctx of where command was invoked #define EX_PREVIEW 0x8000000u // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c5ba750eb3..dc2b7247f1 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -726,7 +726,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (cstack.cs_idx >= 0) { // If a sourced file or executed function ran to its end, report the // unclosed conditional. - if (!got_int && !did_throw + if (!got_int && !did_throw && !aborting() && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -807,6 +807,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) next = messages->next; emsg(messages->msg); xfree(messages->msg); + xfree(messages->sfile); xfree(messages); messages = next; } while (messages != NULL); @@ -1059,7 +1060,7 @@ static int current_tab_nr(tabpage_T *tab) #define LAST_TAB_NR current_tab_nr(NULL) /// Figure out the address type for ":wincmd". -static void get_wincmd_addr_type(char *arg, exarg_T *eap) +static void get_wincmd_addr_type(const char *arg, exarg_T *eap) { switch (*arg) { case 'S': @@ -2837,7 +2838,7 @@ theend: /// @param pp start of command /// @param cmd name of command /// @param len required length -bool checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, const char *cmd, int len) { int i; @@ -3351,7 +3352,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { // skip "/pat/" - cmd = skip_regexp(cmd, c, p_magic, NULL); + cmd = skip_regexp(cmd, c, p_magic); if (*cmd == c) { cmd++; } @@ -5212,7 +5213,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) old_curwin == NULL ? curwin : NULL); } else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit) || *eap->arg != NUL) { - // Can't edit another file when "curbuf->b_ro_lockec" is set. Only ":edit" + // Can't edit another file when "curbuf->b_ro_locked" is set. Only ":edit" // can bring us here, others are stopped earlier. if (*eap->arg != NUL && curbuf_locked()) { return; @@ -5654,7 +5655,7 @@ void do_sleep(long msec) { ui_flush(); // flush before waiting for (long left = msec; !got_int && left > 0; left -= 1000L) { - int next = left > 1000l ? 1000 : (int)left; + int next = left > 1000L ? 1000 : (int)left; LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)next, got_int); os_breakcheck(); } @@ -6494,7 +6495,7 @@ static void ex_findpat(exarg_T *eap) if (*eap->arg == '/') { // Match regexp, not just whole words whole = false; eap->arg++; - char *p = skip_regexp(eap->arg, '/', p_magic, NULL); + char *p = skip_regexp(eap->arg, '/', p_magic); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -6559,7 +6560,7 @@ static void ex_tag(exarg_T *eap) ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); } -static void ex_tag_cmd(exarg_T *eap, char *name) +static void ex_tag_cmd(exarg_T *eap, const char *name) { int cmd; @@ -6685,8 +6686,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// @return an allocated string if a valid match was found. /// Returns NULL if no match was found. "usedlen" then still contains the /// number of characters to skip. -char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, - int *escaped, bool empty_is_error) +char_u *eval_vars(char_u *src, const char_u *srcstart, size_t *usedlen, linenr_T *lnump, + char **errormsg, int *escaped, bool empty_is_error) { char *result; char *resultbuf = NULL; @@ -7266,7 +7267,7 @@ static void ex_terminal(exarg_T *eap) void verify_command(char *cmd) { - if (strcmp("smile", cmd)) { + if (strcmp("smile", cmd) != 0) { return; // acceptable non-existing command } msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx" diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 09bd88c947..8c2ac895cb 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -900,12 +900,12 @@ void ex_else(exarg_T *eap) if (eap->cmdidx == CMD_elseif) { bool error; result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + // When throwing error exceptions, we want to throw always the first // of several errors in a row. This is what actually happens when // a conditional error was detected above and there is another failure // when parsing the expression. Since the skip flag is set in this // case, the parsing error will be ignored by emsg(). - if (!skip && !error) { if (result) { cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE; @@ -1083,7 +1083,7 @@ void ex_endwhile(exarg_T *eap) } // Try to find the matching ":while" and report what's missing. for (idx = cstack->cs_idx; idx > 0; idx--) { - fl = cstack->cs_flags[idx]; + fl = cstack->cs_flags[idx]; if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. // Ignore the ":endwhile"/":endfor". @@ -1296,7 +1296,10 @@ void ex_catch(exarg_T *eap) eap->nextcmd = find_nextcmd(eap->arg); } else { pat = eap->arg + 1; - end = skip_regexp(pat, *eap->arg, true, NULL); + end = skip_regexp_err(pat, *eap->arg, true); + if (end == NULL) { + give_up = true; + } } if (!give_up) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a1e4bc96b5..1d242e4ed2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -314,7 +314,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s p = skipwhite(p); delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; *search_delim = delim; - end = skip_regexp(p, delim, p_magic, NULL); + end = skip_regexp(p, delim, p_magic); use_last_pat = end == p && *end == delim; if (end == p && !use_last_pat) { diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index eae3b4af1a..887ba5400a 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -240,7 +240,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne } /// @return the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp) +static char *ses_get_fname(buf_T *buf, const unsigned *flagp) { // Use the short file name if the current directory is known at the time // the session file will be sourced. diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index c41916d40a..1434ba415b 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1413,8 +1413,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // When the file doesn't exist, try adding parts of 'suffixesadd'. buf = (char *)suffixes; for (;;) { - if ( - (os_path_exists(NameBuff) + if ((os_path_exists(NameBuff) && (find_what == FINDFILE_BOTH || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7f844bc8f7..c780f64a7a 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -524,6 +524,8 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // Don't change 'eol' if reading from buffer as it will already be // correctly set when reading stdin. if (!read_buffer) { + curbuf->b_p_eof = false; + curbuf->b_start_eof = false; curbuf->b_p_eol = true; curbuf->b_start_eol = true; } @@ -1622,16 +1624,28 @@ failed: error = false; } + // In Dos format ignore a trailing CTRL-Z, unless 'binary' is set. + // In old days the file length was in sector count and the CTRL-Z the + // marker where the file really ended. Assuming we write it to a file + // system that keeps file length properly the CTRL-Z should be dropped. + // Set the 'endoffile' option so the user can decide what to write later. + // In Unix format the CTRL-Z is just another character. + if (linerest != 0 + && !curbuf->b_p_bin + && fileformat == EOL_DOS + && ptr[-1] == Ctrl_Z) { + ptr--; + linerest--; + if (set_options) { + curbuf->b_p_eof = true; + } + } + // If we get EOF in the middle of a line, note the fact and // complete the line ourselves. - // In Dos format ignore a trailing CTRL-Z, unless 'binary' set. if (!error && !got_int - && linerest != 0 - && !(!curbuf->b_p_bin - && fileformat == EOL_DOS - && *line_start == Ctrl_Z - && ptr == line_start + 1)) { + && linerest != 0) { // remember for when writing if (set_options) { curbuf->b_p_eol = false; @@ -1933,7 +1947,7 @@ bool is_dev_fd_file(char *fname) /// @param linecnt line count before reading more bytes /// @param p start of more bytes read /// @param endp end of more bytes read -static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) +static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, const char_u *endp) { char_u *s; linenr_T lnum; @@ -2767,10 +2781,11 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #endif // copy the file - if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) - != 0) { - SET_ERRMSG(_("E506: Can't write to backup file " - "(add ! to override)")); + if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) != 0) { + SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); + XFREE_CLEAR(backup); + backup = NULL; + continue; } #ifdef UNIX @@ -2781,6 +2796,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #ifdef HAVE_ACL mch_set_acl((char_u *)backup, acl); #endif + SET_ERRMSG(NULL); break; } } @@ -3242,6 +3258,11 @@ restore_backup: nchars += len; } + if (!buf->b_p_fixeol && buf->b_p_eof) { + // write trailing CTRL-Z + (void)write_eintr(write_info.bw_fd, "\x1a", 1); + } + // Stop when writing done or an error was encountered. if (!checking_conversion || end == 0) { break; @@ -4097,7 +4118,7 @@ static int get_fio_flags(const char_u *name) /// /// @return the name of the encoding and set "*lenp" to the length or, /// NULL when no BOM found. -static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) +static char_u *check_for_bom(const char_u *p, long size, int *lenp, int flags) { char *name = NULL; int len = 2; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 04e6818d53..02fbbc20db 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1083,7 +1083,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) { fold_T *fp; linenr_T lnum_rel = lnum; - int level = 0; + int level = 0; // Recursively search for a fold that contains "lnum". garray_T *gap = &wp->w_folds; diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 6dba6552b3..4e4dd83367 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -30,6 +30,7 @@ local type_flags={ local redraw_flags={ statuslines='P_RSTAT', + tabline = 'P_RTABL', current_window='P_RWIN', current_window_only='P_RWINONLY', current_buffer='P_RBUF', diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index ca8e07bba3..f76107c401 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -509,7 +509,7 @@ void AppendToRedobuffLit(const char *str, int len) s--; } if (s > start) { - add_buff(&redobuff, start, (long)(s - start)); + add_buff(&redobuff, start, s - start); } if (*s == NUL || (len >= 0 && s - str >= len)) { @@ -1886,7 +1886,7 @@ static int check_simplify_modifier(int max_offset) /// - When there is no match yet, return map_result_nomatch, need to get more /// typeahead. /// - On failure (out of memory) return map_result_fail. -static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) +static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth) { mapblock_T *mp = NULL; mapblock_T *mp2; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 5a38585bb1..14266fc859 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -194,6 +194,12 @@ EXTERN int emsg_skip INIT(= 0); // don't display errors for // expression that is skipped EXTERN bool emsg_severe INIT(= false); // use message of next of several // emsg() calls for throw +// used by assert_fails() +EXTERN bool emsg_assert_fails_used INIT(= false); +EXTERN char *emsg_assert_fails_msg INIT(= NULL); +EXTERN long emsg_assert_fails_lnum INIT(= 0); +EXTERN char *emsg_assert_fails_context INIT(= NULL); + EXTERN bool did_endif INIT(= false); // just had ":endif" EXTERN dict_T vimvardict; // Dictionary with v: variables EXTERN dict_T globvardict; // Dictionary with g: variables @@ -613,7 +619,7 @@ EXTERN int State INIT(= MODE_NORMAL); EXTERN bool debug_mode INIT(= false); EXTERN bool finish_op INIT(= false); // true while an operator is pending EXTERN long opcount INIT(= 0); // count for pending operator -EXTERN int motion_force INIT(=0); // motion force for pending operator +EXTERN int motion_force INIT(= 0); // motion force for pending operator // Ex Mode (Q) state EXTERN bool exmode_active INIT(= false); // true if Ex mode is active @@ -621,7 +627,7 @@ EXTERN bool exmode_active INIT(= false); // true if Ex mode is active /// Flag set when normal_check() should return 0 when entering Ex mode. EXTERN bool pending_exmode_active INIT(= false); -EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. +EXTERN bool ex_no_reprint INIT(= false); // No need to print after z or p. // 'inccommand' command preview state EXTERN bool cmdpreview INIT(= false); @@ -997,8 +1003,6 @@ EXTERN char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lu EXTERN char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); -EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string required")); - EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 7cc434a197..7345e9cc35 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -140,8 +140,7 @@ static int page_count; #define OPT_MBFONT_BOLDOBLIQUE 5 #define OPT_MBFONT_NUM_OPTIONS 6 -static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = -{ +static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = { { "c", false, 0, NULL, 0, false }, { "a", false, 0, NULL, 0, false }, { "r", false, 0, NULL, 0, false }, @@ -208,8 +207,7 @@ typedef enum { } PrtResourceType; // String versions of PS resource types -static const char *const prt_resource_types[] = -{ +static const char *const prt_resource_types[] = { [PRT_RESOURCE_TYPE_PROCSET] = "procset", [PRT_RESOURCE_TYPE_ENCODING] = "encoding", [PRT_RESOURCE_TYPE_CMAP] = "cmap", @@ -945,8 +943,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T #define PRT_MEDIASIZE_LEN ARRAY_SIZE(prt_mediasize) -static struct prt_mediasize_S prt_mediasize[] = -{ +static struct prt_mediasize_S prt_mediasize[] = { { "A4", 595.0, 842.0 }, { "letter", 612.0, 792.0 }, { "10x14", 720.0, 1008.0 }, @@ -969,8 +966,7 @@ static struct prt_mediasize_S prt_mediasize[] = #define PRT_PS_FONT_BOLDOBLIQUE (3) // Standard font metrics for Courier family -static struct prt_ps_font_S prt_ps_courier_font = -{ +static struct prt_ps_font_S prt_ps_courier_font = { 600, -100, 50, -250, 805, @@ -978,8 +974,7 @@ static struct prt_ps_font_S prt_ps_courier_font = }; // Generic font metrics for multi-byte fonts -static struct prt_ps_font_S prt_ps_mb_font = -{ +static struct prt_ps_font_S prt_ps_mb_font = { 1000, -100, 50, -250, 805, @@ -999,8 +994,7 @@ static struct prt_ps_font_S *prt_ps_font; #define CS_KANJITALK7 (0x80) // Japanese encodings and charsets -static struct prt_ps_encoding_S j_encodings[] = -{ +static struct prt_ps_encoding_S j_encodings[] = { { "iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990| CS_NEC) }, { "euc-jp", "EUC", (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990) }, @@ -1010,8 +1004,7 @@ static struct prt_ps_encoding_S j_encodings[] = { "ucs-2", "UCS2", CS_JIS_X_1990 }, { "utf-8", "UTF8", CS_JIS_X_1990 } }; -static struct prt_ps_charset_S j_charsets[] = -{ +static struct prt_ps_charset_S j_charsets[] = { { "JIS_C_1978", "78", CS_JIS_C_1978 }, { "JIS_X_1983", NULL, CS_JIS_X_1983 }, { "JIS_X_1990", "Hojo", CS_JIS_X_1990 }, @@ -1031,8 +1024,7 @@ static struct prt_ps_charset_S j_charsets[] = #define CS_SC_ISO10646 (0x40) // Simplified Chinese encodings and charsets -static struct prt_ps_encoding_S sc_encodings[] = -{ +static struct prt_ps_encoding_S sc_encodings[] = { { "iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90) }, { "gb18030", NULL, CS_GBK2K }, { "euc-cn", "EUC", (CS_GB_2312_80|CS_GBT_12345_90|CS_SC_MAC| @@ -1041,8 +1033,7 @@ static struct prt_ps_encoding_S sc_encodings[] = { "ucs-2", "UCS2", CS_SC_ISO10646 }, { "utf-8", "UTF8", CS_SC_ISO10646 } }; -static struct prt_ps_charset_S sc_charsets[] = -{ +static struct prt_ps_charset_S sc_charsets[] = { { "GB_2312-80", "GB", CS_GB_2312_80 }, { "GBT_12345-90", "GBT", CS_GBT_12345_90 }, { "MAC", "GBpc", CS_SC_MAC }, @@ -1067,8 +1058,7 @@ static struct prt_ps_charset_S sc_charsets[] = #define CS_TC_ISO10646 (0x1000) // Traditional Chinese encodings and charsets -static struct prt_ps_encoding_S tc_encodings[] = -{ +static struct prt_ps_encoding_S tc_encodings[] = { { "iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2) }, { "euc-tw", "EUC", CS_CNS_PLANE_1_2 }, { "big5", "B5", (CS_B5|CS_ETEN|CS_HK_GCCS|CS_HK_SCS| @@ -1080,8 +1070,7 @@ static struct prt_ps_encoding_S tc_encodings[] = { "utf-16", "UTF16", CS_TC_ISO10646 }, { "utf-32", "UTF32", CS_TC_ISO10646 } }; -static struct prt_ps_charset_S tc_charsets[] = -{ +static struct prt_ps_charset_S tc_charsets[] = { { "CNS_1992_1", "CNS1", CS_CNS_PLANE_1 }, { "CNS_1992_2", "CNS2", CS_CNS_PLANE_2 }, { "CNS_1993", "CNS", CS_CNS_PLANE_1_2 }, @@ -1104,8 +1093,7 @@ static struct prt_ps_charset_S tc_charsets[] = #define CS_KR_ISO10646 (0x08) // Korean encodings and charsets -static struct prt_ps_encoding_S k_encodings[] = -{ +static struct prt_ps_encoding_S k_encodings[] = { { "iso-2022-kr", NULL, CS_KR_X_1992 }, { "euc-kr", "EUC", (CS_KR_X_1992|CS_KR_MAC) }, { "johab", "Johab", CS_KR_X_1992 }, @@ -1115,8 +1103,7 @@ static struct prt_ps_encoding_S k_encodings[] = { "ucs-2", "UCS2", CS_KR_ISO10646 }, { "utf-8", "UTF8", CS_KR_ISO10646 } }; -static struct prt_ps_charset_S k_charsets[] = -{ +static struct prt_ps_charset_S k_charsets[] = { { "KS_X_1992", "KSC", CS_KR_X_1992 }, { "CP1361", "KSC", CS_KR_X_1992 }, { "MAC", "KSCpc", CS_KR_MAC }, @@ -1126,8 +1113,7 @@ static struct prt_ps_charset_S k_charsets[] = { "ISO10646", "UniKS", CS_KR_ISO10646 } }; -static struct prt_ps_mbfont_S prt_ps_mbfonts[] = -{ +static struct prt_ps_mbfont_S prt_ps_mbfonts[] = { { ARRAY_SIZE(j_encodings), j_encodings, @@ -1193,8 +1179,7 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] = #define PRT_DSC_ENDCOMMENTS "%%EndComments:" #define SIZEOF_CSTR(s) (sizeof(s) - 1) -static struct prt_dsc_comment_S prt_dsc_table[] = -{ +static struct prt_dsc_comment_S prt_dsc_table[] = { { PRT_DSC_TITLE, SIZEOF_CSTR(PRT_DSC_TITLE), PRT_DSC_TITLE_TYPE }, { PRT_DSC_VERSION, SIZEOF_CSTR(PRT_DSC_VERSION), PRT_DSC_VERSION_TYPE }, @@ -1741,7 +1726,7 @@ static bool prt_check_resource(const struct prt_ps_resource_S *resource, const c FUNC_ATTR_NONNULL_ALL { // Version number m.n should match, the revision number does not matter - if (STRNCMP(resource->version, version, strlen(version))) { + if (STRNCMP(resource->version, version, strlen(version)) != 0) { semsg(_("E621: \"%s\" resource file has wrong version"), resource->name); return false; diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 4c253480be..9a09118939 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -851,7 +851,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) bool did_highlight_changed = false; // If no argument, list current highlighting. - if (ends_excmd((uint8_t)(*line))) { + if (!init && ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { // TODO(brammool): only call when the group has attributes set highlight_list_one(i); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 3f08aa7043..d372e41459 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -101,7 +101,7 @@ bool tabstop_set(char *var, long **array) /// 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) +int tabstop_padding(colnr_T col, long ts_arg, const long *vts) { long ts = ts_arg == 0 ? 8 : ts_arg; colnr_T tabcol = 0; @@ -129,7 +129,7 @@ int tabstop_padding(colnr_T col, long ts_arg, long *vts) } /// Find the size of the tab that covers a particular column. -int tabstop_at(colnr_T col, long ts, long *vts) +int tabstop_at(colnr_T col, long ts, const long *vts) { colnr_T tabcol = 0; int t; @@ -178,7 +178,7 @@ colnr_T tabstop_start(colnr_T col, long ts, long *vts) /// 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, +void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long *vts, int *ntabs, int *nspcs) { int spaces = end_col - start_col; @@ -242,7 +242,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, } /// See if two tabstop arrays contain the same values. -bool tabstop_eq(long *ts1, long *ts2) +bool tabstop_eq(const long *ts1, const long *ts2) { int t; @@ -266,7 +266,7 @@ bool tabstop_eq(long *ts1, long *ts2) } /// Copy a tabstop array, allocating space for the new array. -int *tabstop_copy(long *oldts) +int *tabstop_copy(const long *oldts) { long *newts; int t; @@ -743,6 +743,7 @@ bool briopt_check(win_T *wp) int bri_min = 20; bool bri_sbr = false; int bri_list = 0; + int bri_vcol = 0; char *p = wp->w_p_briopt; while (*p != NUL) { @@ -759,6 +760,9 @@ bool briopt_check(win_T *wp) } else if (STRNCMP(p, "list:", 5) == 0) { p += 5; bri_list = (int)getdigits(&p, false, 0); + } else if (STRNCMP(p, "column:", 7) == 0) { + p += 7; + bri_vcol = (int)getdigits(&p, false, 0); } if (*p != ',' && *p != NUL) { return false; @@ -771,7 +775,8 @@ bool briopt_check(win_T *wp) wp->w_briopt_shift = bri_shift; wp->w_briopt_min = bri_min; wp->w_briopt_sbr = bri_sbr; - wp->w_briopt_list = bri_list; + wp->w_briopt_list = bri_list; + wp->w_briopt_vcol = bri_vcol; return true; } @@ -783,51 +788,78 @@ int get_breakindent_win(win_T *wp, char_u *line) FUNC_ATTR_NONNULL_ALL { static int prev_indent = 0; // Cached indent value. - static long prev_ts = 0; // Cached tabstop value. + static long prev_ts = 0L; // 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. + static long *prev_vts = NULL; // Cached vartabs values. + static int prev_list = 0; // cached list value + static int prev_listopt = 0; // cached w_p_briopt_list value + static char *prev_flp = NULL; // cached formatlistpat value int bri = 0; // window width minus window margin space, i.e. what rests for text const int eff_wwidth = wp->w_width_inner - ((wp->w_p_nu || wp->w_p_rnu) && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); - // used cached indent, unless pointer or 'tabstop' changed + // used cached indent, unless + // - line pointer changed + // - 'tabstop' changed + // - 'briopt_list changed' changed or + // - 'formatlistpattern' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts || prev_tick != buf_get_changedtick(wp->w_buffer) + || prev_listopt != wp->w_briopt_list + || (prev_flp == NULL || (strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0)) || 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_vts = wp->w_buffer->b_p_vts_array; - prev_indent = get_indent_str_vtab((char *)line, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array, - wp->w_p_list); + if (wp->w_briopt_vcol == 0) { + prev_indent = get_indent_str_vtab((char *)line, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array, + wp->w_p_list); + } + prev_listopt = wp->w_briopt_list; + prev_list = 0; + xfree(prev_flp); + prev_flp = xstrdup(get_flp_value(wp->w_buffer)); + // add additional indent for numbered lists + if (wp->w_briopt_list != 0 && wp->w_briopt_vcol == 0) { + regmatch_T regmatch = { + .regprog = vim_regcomp(prev_flp, RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), + }; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = false; + if (vim_regexec(®match, (char *)line, 0)) { + if (wp->w_briopt_list > 0) { + prev_list += wp->w_briopt_list; + } else { + prev_list = (int)(*regmatch.endp - *regmatch.startp); + } + } + vim_regfree(regmatch.regprog); + } + } + } + if (wp->w_briopt_vcol != 0) { + // column value has priority + bri = wp->w_briopt_vcol; + prev_list = 0; + } else { + bri = prev_indent + wp->w_briopt_shift; } - bri = prev_indent + wp->w_briopt_shift; // Add offset for number column, if 'n' is in 'cpoptions' bri += win_col_off2(wp); // add additional indent for numbered lists if (wp->w_briopt_list != 0) { - regmatch_T regmatch = { - .regprog = vim_regcomp(curbuf->b_p_flp, - RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), - }; - - if (regmatch.regprog != NULL) { - regmatch.rm_ic = false; - if (vim_regexec(®match, (char *)line, 0)) { - if (wp->w_briopt_list > 0) { - bri += wp->w_briopt_list; - } else { - bri = (int)(*regmatch.endp - *regmatch.startp); - } - } - vim_regfree(regmatch.regprog); + if (wp->w_briopt_list > 0) { + bri += prev_list; + } else { + bri = prev_list; } } diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 0f29ae9806..9f4c02da19 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -77,8 +77,7 @@ #define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] /// Message for CTRL-X mode, index is ctrl_x_mode. -static char *ctrl_x_msgs[] = -{ +static char *ctrl_x_msgs[] = { N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), NULL, // CTRL_X_SCROLL: depends on state @@ -572,7 +571,7 @@ bool ins_compl_accept_char(int c) /// Get the completed text by inferring the case of the originally typed text. /// If the result is in allocated memory "tofree" is set to it. -static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_char_len, +static char_u *ins_compl_infercase_gettext(const char_u *str, int char_len, int compl_char_len, int min_len, char **tofree) { bool has_lower = false; @@ -1378,7 +1377,8 @@ theend: /// skipping the word at 'skip_word'. /// /// @return OK on success. -static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, char_u *skip_word) +static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, + const char_u *skip_word) { int status = OK; diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 61dc2ac035..de3c1dbf84 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -46,8 +46,7 @@ static const struct modmasktable { #define MOD_KEYS_ENTRY_SIZE 5 -static char_u modifier_keys_table[] = -{ +static char_u modifier_keys_table[] = { // mod mask with modifier without modifier MOD_MASK_SHIFT, '&', '9', '@', '1', // begin MOD_MASK_SHIFT, '&', '0', '@', '2', // cancel @@ -347,8 +346,7 @@ static struct mousetable { int button; // Which mouse button is it? bool is_click; // Is it a mouse button click event? bool is_drag; // Is it a mouse drag event? -} mouse_table[] = -{ +} mouse_table[] = { { KE_LEFTMOUSE, MOUSE_LEFT, true, false }, { KE_LEFTDRAG, MOUSE_LEFT, false, true }, { KE_LEFTRELEASE, MOUSE_LEFT, false, false }, diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c new file mode 100644 index 0000000000..04209bb836 --- /dev/null +++ b/src/nvim/linematch.c @@ -0,0 +1,376 @@ +#include "nvim/linematch.h" +#include "nvim/memory.h" +#include "nvim/vim.h" + +// struct for running the diff linematch algorithm +typedef struct { + int *df_decision; // to keep track of this path traveled + int df_lev_score; // to keep track of the total score of this path + size_t df_path_idx; // current index of this path +} diffcmppath_T; + +#define LN_MAX_BUFS 8 + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.c.generated.h" +#endif + +static size_t line_len(const char *s) +{ + char *end = strchr(s, '\n'); + if (end) { + return (size_t)(end - s); + } + return STRLEN(s); +} + +/// Same as matching_chars but ignore whitespace +/// +/// @param s1 +/// @param s2 +static int matching_chars_iwhite(const char *s1, const char *s2) +{ + // the newly processed strings that will be compared + // delete the white space characters, and/or replace all upper case with lower + char *strsproc[2]; + const char *strsorig[2] = { s1, s2 }; + for (int k = 0; k < 2; k++) { + size_t d = 0; + size_t i = 0; + size_t slen = line_len(strsorig[k]); + strsproc[k] = xmalloc((slen + 1) * sizeof(char)); + while (d + i < slen) { + char e = strsorig[k][i + d]; + if (e != ' ' && e != '\t') { + strsproc[k][i] = e; + i++; + } else { + d++; + } + } + strsproc[k][i] = '\0'; + } + int matching = matching_chars(strsproc[0], strsproc[1]); + xfree(strsproc[0]); + xfree(strsproc[1]); + return matching; +} + +/// update the path of a point in the diff linematch algorithm +/// @param diffcmppath +/// @param score +/// @param to +/// @param from +/// @param choice +static void update_path_flat(diffcmppath_T *diffcmppath, int score, size_t to, size_t from, + const int choice) +{ + size_t path_idx = diffcmppath[from].df_path_idx; + + for (size_t k = 0; k < path_idx; k++) { + diffcmppath[to].df_decision[k] = diffcmppath[from].df_decision[k]; + } + + diffcmppath[to].df_decision[path_idx] = choice; + diffcmppath[to].df_lev_score = score; + diffcmppath[to].df_path_idx = path_idx + 1; +} + +#define MATCH_CHAR_MAX_LEN 500 + +/// Return matching characters between "s1" and "s2" whilst respecting sequence order. +/// Consider the case of two strings 'AAACCC' and 'CCCAAA', the +/// return value from this function will be 3, either to match +/// the 3 C's, or the 3 A's. +/// +/// Examples: +/// matching_chars("aabc", "acba") -> 2 // 'a' and 'b' in common +/// matching_chars("123hello567", "he123ll567o") -> 8 // '123', 'll' and '567' in common +/// matching_chars("abcdefg", "gfedcba") -> 1 // all characters in common, +/// // but only at most 1 in sequence +/// +/// @param s1 +/// @param s2 +static int matching_chars(const char *s1, const char *s2) +{ + size_t s1len = MIN(MATCH_CHAR_MAX_LEN, line_len(s1)); + size_t s2len = MIN(MATCH_CHAR_MAX_LEN, line_len(s2)); + int matrix[2][MATCH_CHAR_MAX_LEN] = { 0 }; + bool icur = 1; // save space by storing only two rows for i axis + for (size_t i = 0; i < s1len; i++) { + icur = !icur; + int *e1 = matrix[icur]; + int *e2 = matrix[!icur]; + for (size_t j = 0; j < s2len; j++) { + // skip char in s1 + if (e2[j + 1] > e1[j + 1]) { + e1[j + 1] = e2[j + 1]; + } + // skip char in s2 + if (e1[j] > e1[j + 1]) { + e1[j + 1] = e1[j]; + } + // compare char in s1 and s2 + if ((s1[i] == s2[j]) && (e2[j] + 1) > e1[j + 1]) { + e1[j + 1] = e2[j] + 1; + } + } + } + return matrix[icur][s2len]; +} + +/// count the matching characters between a variable number of strings "sp" +/// mark the strings that have already been compared to extract them later +/// without re-running the character match counting. +/// @param sp +/// @param fomvals +/// @param n +static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite) +{ + int matched_chars = 0; + int matched = 0; + for (size_t i = 0; i < n; i++) { + for (size_t j = i + 1; j < n; j++) { + if (sp[i] != NULL && sp[j] != NULL) { + matched++; + // TODO(lewis6991): handle whitespace ignoring higher up in the stack + matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j]) + : matching_chars(sp[i], sp[j]); + } + } + } + + // prioritize a match of 3 (or more lines) equally to a match of 2 lines + if (matched >= 2) { + matched_chars *= 2; + matched_chars /= matched; + } + + return matched_chars; +} + +void fastforward_buf_to_lnum(const char **s, long lnum) +{ + for (long i = 0; i < lnum - 1; i++) { + *s = strchr(*s, '\n'); + (*s)++; + } +} + +/// try all the different ways to compare these lines and use the one that +/// results in the most matching characters +/// @param df_iters +/// @param paths +/// @param npaths +/// @param path_idx +/// @param choice +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths, + const int path_idx, int *choice, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (path_idx == npaths) { + if ((*choice) > 0) { + int from_vals[LN_MAX_BUFS]; + const int *to_vals = df_iters; + const char *current_lines[LN_MAX_BUFS]; + for (size_t k = 0; k < ndiffs; k++) { + from_vals[k] = df_iters[k]; + // get the index at all of the places + if ((*choice) & (1 << k)) { + from_vals[k]--; + const char *p = diff_blk[k]; + fastforward_buf_to_lnum(&p, df_iters[k]); + current_lines[k] = p; + } else { + current_lines[k] = NULL; + } + } + size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs); + size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs); + int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite); + int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars; + if (score > diffcmppath[unwrapped_idx_to].df_lev_score) { + update_path_flat(diffcmppath, score, unwrapped_idx_to, unwrapped_idx_from, *choice); + } + } else { + // initialize the 0, 0, 0 ... choice + size_t i = 0; + while (i < ndiffs && df_iters[i] == 0) { + i++; + if (i == ndiffs) { + diffcmppath[0].df_lev_score = 0; + diffcmppath[0].df_path_idx = 0; + } + } + } + return; + } + size_t bit_place = paths[path_idx]; + *(choice) |= (1 << bit_place); // set it to 1 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + *(choice) &= ~(1 << bit_place); // set it to 0 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); +} + +/// unwrap indexes to access n dimensional tensor +/// @param values +/// @param diff_len +/// @param ndiffs +static size_t unwrap_indexes(const int *values, const int *diff_len, const size_t ndiffs) +{ + size_t num_unwrap_scalar = 1; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar *= (size_t)diff_len[k] + 1; + } + + size_t path_idx = 0; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar /= (size_t)diff_len[k] + 1; + + // (k == 0) space optimization + int n = k == 0 ? values[k] % 2 : values[k]; + path_idx += num_unwrap_scalar * (size_t)n; + } + return path_idx; +} + +/// populate the values of the linematch algorithm tensor, and find the best +/// decision for how to compare the relevant lines from each of the buffers at +/// each point in the tensor +/// @param df_iters +/// @param ch_dim +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (ch_dim == ndiffs) { + int npaths = 0; + size_t paths[LN_MAX_BUFS]; + + for (size_t j = 0; j < ndiffs; j++) { + if (df_iters[j] > 0) { + paths[npaths] = j; + npaths++; + } + } + int choice = 0; + size_t unwrapper_idx_to = unwrap_indexes(df_iters, diff_len, ndiffs); + diffcmppath[unwrapper_idx_to].df_lev_score = -1; + try_possible_paths(df_iters, paths, npaths, 0, &choice, diffcmppath, + diff_len, ndiffs, diff_blk, iwhite); + return; + } + + for (int i = 0; i <= diff_len[ch_dim]; i++) { + df_iters[ch_dim] = i; + populate_tensor(df_iters, ch_dim + 1, diffcmppath, diff_len, + ndiffs, diff_blk, iwhite); + } +} + +/// algorithm to find an optimal alignment of lines of a diff block with 2 or +/// more files. The algorithm is generalized to work for any number of files +/// which corresponds to another dimmension added to the tensor used in the +/// algorithm +/// +/// for questions and information about the linematch algorithm please contact +/// Jonathon White (jonathonwhite@protonmail.com) +/// +/// for explanation, a summary of the algorithm in 3 dimmensions (3 files +/// compared) follows +/// +/// The 3d case (for 3 buffers) of the algorithm implemented when diffopt +/// 'linematch' is enabled. The algorithm constructs a 3d tensor to +/// compare a diff between 3 buffers. The dimmensions of the tensor are +/// the length of the diff in each buffer plus 1 A path is constructed by +/// moving from one edge of the cube/3d tensor to the opposite edge. +/// Motions from one cell of the cube to the next represent decisions. In +/// a 3d cube, there are a total of 7 decisions that can be made, +/// represented by the enum df_path3_choice which is defined in +/// buffer_defs.h a comparison of buffer 0 and 1 represents a motion +/// toward the opposite edge of the cube with components along the 0 and +/// 1 axes. a comparison of buffer 0, 1, and 2 represents a motion +/// toward the opposite edge of the cube with components along the 0, 1, +/// and 2 axes. A skip of buffer 0 represents a motion along only the 0 +/// axis. For each action, a point value is awarded, and the path is +/// saved for reference later, if it is found to have been the optimal +/// path. The optimal path has the highest score. The score is +/// calculated as the summation of the total characters matching between +/// all of the lines which were compared. The structure of the algorithm +/// is that of a dynamic programming problem. We can calculate a point +/// i,j,k in the cube as a function of i-1, j-1, and k-1. To find the +/// score and path at point i,j,k, we must determine which path we want +/// to use, this is done by looking at the possibilities and choosing +/// the one which results in the local highest score. The total highest +/// scored path is, then in the end represented by the cell in the +/// opposite corner from the start location. The entire algorithm +/// consits of populating the 3d cube with the optimal paths from which +/// it may have came. +/// +/// Optimizations: +/// As the function to calculate the cell of a tensor at point i,j,k is a +/// function of the cells at i-1, j-1, k-1, the whole tensor doesn't need +/// to be stored in memory at once. In the case of the 3d cube, only two +/// slices (along k and j axis) are stored in memory. For the 2d matrix +/// (for 2 files), only two rows are stored at a time. The next/previous +/// slice (or row) is always calculated from the other, and they alternate +/// at each iteration. +/// In the 3d case, 3 arrays are populated to memorize the score (matched +/// characters) of the 3 buffers, so a redundant calculation of the +/// scores does not occur +/// @param diff_blk +/// @param diff_len +/// @param ndiffs +/// @param [out] [allocated] decisions +/// @return the length of decisions +size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs, + int **decisions, bool iwhite) +{ + assert(ndiffs <= LN_MAX_BUFS); + + size_t memsize = 1; + size_t memsize_decisions = 0; + for (size_t i = 0; i < ndiffs; i++) { + assert(diff_len[i] >= 0); + memsize *= i == 0 ? 2 : (size_t)(diff_len[i] + 1); + memsize_decisions += (size_t)diff_len[i]; + } + + // create the flattened path matrix + diffcmppath_T *diffcmppath = xmalloc(sizeof(diffcmppath_T) * memsize); + // allocate memory here + for (size_t i = 0; i < memsize; i++) { + diffcmppath[i].df_decision = xmalloc(memsize_decisions * sizeof(int)); + } + + // memory for avoiding repetitive calculations of score + int df_iters[LN_MAX_BUFS]; + populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + + const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs); + const size_t best_path_idx = diffcmppath[u].df_path_idx; + const int *best_path_decisions = diffcmppath[u].df_decision; + + *decisions = xmalloc(sizeof(int) * best_path_idx); + for (size_t i = 0; i < best_path_idx; i++) { + (*decisions)[i] = best_path_decisions[i]; + } + + for (size_t i = 0; i < memsize; i++) { + xfree(diffcmppath[i].df_decision); + } + xfree(diffcmppath); + + return best_path_idx; +} diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h new file mode 100644 index 0000000000..052d438617 --- /dev/null +++ b/src/nvim/linematch.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LINEMATCH_H +#define NVIM_LINEMATCH_H + +#include <stddef.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.h.generated.h" +#endif + +#endif // NVIM_LINEMATCH_H diff --git a/src/nvim/locale.c b/src/nvim/locale.c index c472d9ba66..f2db9fcc59 100644 --- a/src/nvim/locale.c +++ b/src/nvim/locale.c @@ -42,7 +42,7 @@ static char *get_locale_val(int what) /// @return true when "lang" starts with a valid language name. /// Rejects NULL, empty string, "C", "C.UTF-8" and others. -static bool is_valid_mess_lang(char *lang) +static bool is_valid_mess_lang(const char *lang) { return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index fca07ee146..9d63fe55f9 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -400,7 +400,7 @@ static int nlua_wait(lua_State *lstate) bool fast_only = false; if (lua_top >= 4) { - fast_only = lua_toboolean(lstate, 4); + fast_only = lua_toboolean(lstate, 4); } MultiQueue *loop_events = fast_only || in_fast_callback > 0 diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2b5dfedee..cd542b0e00 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -10,12 +10,16 @@ #include <string.h> #include "nvim/api/private/helpers.h" +#include "nvim/linematch.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/lua/xdiff.h" #include "nvim/vim.h" #include "xdiff/xdiff.h" +#define COMPARED_BUFFER0 (1 << 0) +#define COMPARED_BUFFER1 (1 << 1) + typedef enum { kNluaXdiffModeUnified = 0, kNluaXdiffModeOnHunkCB, @@ -25,12 +29,81 @@ typedef enum { typedef struct { lua_State *lstate; Error *err; + mmfile_t *ma; + mmfile_t *mb; + bool linematch; + bool iwhite; } hunkpriv_t; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/xdiff.c.generated.h" #endif +static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long start_b, long count_b) +{ + // Mimic extra offsets done by xdiff, see: + // src/xdiff/xemit.c:284 + // src/xdiff/xutils.c:(356,368) + if (count_a > 0) { + start_a += 1; + } + if (count_b > 0) { + start_b += 1; + } + lua_createtable(lstate, 0, 0); + lua_pushinteger(lstate, start_a); + lua_rawseti(lstate, -2, 1); + lua_pushinteger(lstate, count_a); + lua_rawseti(lstate, -2, 2); + lua_pushinteger(lstate, start_b); + lua_rawseti(lstate, -2, 3); + lua_pushinteger(lstate, count_b); + lua_rawseti(lstate, -2, 4); + lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); +} + +static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, long start_a, + long count_a, long start_b, long count_b, bool iwhite) +{ + // get the pointer to char of the start of the diff to pass it to linematch algorithm + const char *diff_begin[2] = { ma->ptr, mb->ptr }; + int diff_length[2] = { (int)count_a, (int)count_b }; + + fastforward_buf_to_lnum(&diff_begin[0], start_a + 1); + fastforward_buf_to_lnum(&diff_begin[1], start_b + 1); + + int *decisions = NULL; + size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite); + + long lnuma = start_a, lnumb = start_b; + + long hunkstarta = lnuma; + long hunkstartb = lnumb; + long hunkcounta = 0; + long hunkcountb = 0; + for (size_t i = 0; i < decisions_length; i++) { + if (i && (decisions[i - 1] != decisions[i])) { + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + + hunkstarta = lnuma; + hunkstartb = lnumb; + hunkcounta = 0; + hunkcountb = 0; + // create a new hunk + } + if (decisions[i] & COMPARED_BUFFER0) { + lnuma++; + hunkcounta++; + } + if (decisions[i] & COMPARED_BUFFER1) { + lnumb++; + hunkcountb++; + } + } + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + xfree(decisions); +} + static int write_string(void *priv, mmbuffer_t *mb, int nbuf) { luaL_Buffer *buf = (luaL_Buffer *)priv; @@ -52,30 +125,15 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf) // hunk_func callback used when opts.hunk_lines = true static int hunk_locations_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data) { - // Mimic extra offsets done by xdiff, see: - // src/xdiff/xemit.c:284 - // src/xdiff/xutils.c:(356,368) - if (count_a > 0) { - start_a += 1; - } - if (count_b > 0) { - start_b += 1; + hunkpriv_t *priv = (hunkpriv_t *)cb_data; + lua_State *lstate = priv->lstate; + if (priv->linematch) { + get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b, + priv->iwhite); + } else { + lua_pushhunk(lstate, start_a, count_a, start_b, count_b); } - lua_State *lstate = (lua_State *)cb_data; - lua_createtable(lstate, 0, 0); - - lua_pushinteger(lstate, start_a); - lua_rawseti(lstate, -2, 1); - lua_pushinteger(lstate, count_a); - lua_rawseti(lstate, -2, 2); - lua_pushinteger(lstate, start_b); - lua_rawseti(lstate, -2, 3); - lua_pushinteger(lstate, count_b); - lua_rawseti(lstate, -2, 4); - - lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); - return 0; } @@ -149,7 +207,7 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char * } static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params, - Error *err) + bool *linematch, Error *err) { const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); @@ -205,6 +263,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, goto exit_1; } cfg->interhunkctxlen = v->data.integer; + } else if (strequal("linematch", k.data)) { + *linematch = api_object_to_bool(*v, "linematch", false, err); + if (ERROR_SET(err)) { + goto exit_1; + } } else { struct { const char *name; @@ -244,10 +307,8 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (had_on_hunk) { mode = kNluaXdiffModeOnHunkCB; - cfg->hunk_func = call_on_hunk_cb; } else if (had_result_type_indices) { mode = kNluaXdiffModeLocations; - cfg->hunk_func = hunk_locations_cb; } exit_1: @@ -268,6 +329,7 @@ int nlua_xdl_diff(lua_State *lstate) xdemitconf_t cfg; xpparam_t params; xdemitcb_t ecb; + bool linematch = false; CLEAR_FIELD(cfg); CLEAR_FIELD(params); @@ -280,7 +342,7 @@ int nlua_xdl_diff(lua_State *lstate) return luaL_argerror(lstate, 3, "expected table"); } - mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &err); + mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &linematch, &err); if (ERROR_SET(&err)) { goto exit_0; @@ -288,7 +350,7 @@ int nlua_xdl_diff(lua_State *lstate) } luaL_Buffer buf; - hunkpriv_t *priv = NULL; + hunkpriv_t priv; switch (mode) { case kNluaXdiffModeUnified: luaL_buffinit(lstate, &buf); @@ -296,14 +358,24 @@ int nlua_xdl_diff(lua_State *lstate) ecb.out_line = write_string; break; case kNluaXdiffModeOnHunkCB: - priv = xmalloc(sizeof(*priv)); - priv->lstate = lstate; - priv->err = &err; - ecb.priv = priv; + cfg.hunk_func = call_on_hunk_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .err = &err, + }; + ecb.priv = &priv; break; case kNluaXdiffModeLocations: + cfg.hunk_func = hunk_locations_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .ma = &ma, + .mb = &mb, + .linematch = linematch, + .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0 + }; + ecb.priv = &priv; lua_createtable(lstate, 0, 0); - ecb.priv = lstate; break; } @@ -314,8 +386,6 @@ int nlua_xdl_diff(lua_State *lstate) } } - XFREE_CLEAR(priv); - exit_0: if (ERROR_SET(&err)) { luaL_where(lstate, 1); diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 001bbf7e48..0fff48019b 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -176,7 +176,7 @@ static void showmap(mapblock_T *mp, bool local) // Display the LHS. Get length of what we write. len = (size_t)msg_outtrans_special((char *)mp->m_keys, true, 0); do { - msg_putchar(' '); // padd with blanks + msg_putchar(' '); // pad with blanks len++; } while (len < 12); @@ -1350,7 +1350,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) char **ptr3 = ptr1 + count; while (ptr2 < ptr3) { - if (strcmp(*ptr1, *ptr2)) { + if (strcmp(*ptr1, *ptr2) != 0) { *++ptr1 = *ptr2++; } else { xfree(*ptr2++); diff --git a/src/nvim/mark.h b/src/nvim/mark.h index 6da976e8d3..a3fcf6d7e7 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -78,12 +78,13 @@ static inline int mark_local_index(const char name) : -1)))); } -static inline bool lt(pos_T, pos_T) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool equalpos(pos_T, pos_T) +static inline bool lt(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ltoreq(pos_T, pos_T) +static inline bool equalpos(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline void clearpos(pos_T *) +static inline bool ltoreq(pos_T a, pos_T b) + REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline void clearpos(pos_T *a) REAL_FATTR_ALWAYS_INLINE; /// Return true if position a is before (less than) position b. diff --git a/src/nvim/match.c b/src/nvim/match.c index b422dc0ba8..916bb44d8c 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -1206,7 +1206,7 @@ void ex_match(exarg_T *eap) semsg(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, true, NULL); + end = skip_regexp(p + 1, *p, true); if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { xfree(g); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 33d652a51f..9e34c7e413 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -136,8 +136,7 @@ const uint8_t utf8len_tab_zero[] = { // "iso-8859-n" is handled by enc_canonize() directly. static struct { const char *name; int prop; int codepage; } -enc_canon_table[] = -{ +enc_canon_table[] = { #define IDX_LATIN_1 0 { "latin1", ENC_8BIT + ENC_LATIN1, 1252 }, #define IDX_ISO_2 1 @@ -270,8 +269,7 @@ enc_canon_table[] = // Aliases for encoding names. static struct { const char *name; int canon; } -enc_alias_table[] = -{ +enc_alias_table[] = { { "ansi", IDX_LATIN_1 }, { "iso-8859-1", IDX_LATIN_1 }, { "latin2", IDX_ISO_2 }, @@ -1024,8 +1022,7 @@ bool utf_printable(int c) { // Sorted list of non-overlapping intervals. // 0xd800-0xdfff is reserved for UTF-16, actually illegal. - static struct interval nonprint[] = - { + static struct interval nonprint[] = { { 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0xfffe, 0xffff } @@ -2301,7 +2298,7 @@ void *my_iconv_open(char_u *to, char_u *from) // sequence and set "*unconvlenp" to the length of it. // Returns the converted string in allocated memory. NULL for an error. // If resultlenp is not NULL, sets it to the result length in bytes. -static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen, +static char_u *iconv_string(const vimconv_T *const vcp, const char_u *str, size_t slen, size_t *unconvlenp, size_t *resultlenp) { const char *from; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 56342942db..225e2aeab1 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -169,7 +169,7 @@ struct block0 { char_u b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited long b0_magic_long; // check for byte order of long int b0_magic_int; // check for byte order of int - short b0_magic_short; // check for byte order of short + int16_t b0_magic_short; // check for byte order of short char_u b0_magic_char; // check for last char }; @@ -272,7 +272,7 @@ int ml_open(buf_T *buf) b0p->b0_id[1] = BLOCK0_ID1; b0p->b0_magic_long = B0_MAGIC_LONG; b0p->b0_magic_int = (int)B0_MAGIC_INT; - b0p->b0_magic_short = (short)B0_MAGIC_SHORT; + b0p->b0_magic_short = (int16_t)B0_MAGIC_SHORT; b0p->b0_magic_char = B0_MAGIC_CHAR; xstrlcpy(xstpcpy((char *)b0p->b0_version, "VIM "), Version, 6); long_to_char((long)mfp->mf_page_size, b0p->b0_page_size); @@ -437,7 +437,7 @@ void ml_open_file(buf_T *buf) if (buf->b_spell) { char *fname = vim_tempname(); if (fname != NULL) { - (void)mf_open_file(mfp, (char *)fname); // consumes fname! + (void)mf_open_file(mfp, fname); // consumes fname! } buf->b_may_swap = false; return; @@ -1193,7 +1193,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) // Isolate a directory name from *dirp and put it in dir_name (we know // it is large enough, so use 31000 for length). // Advance dirp to next directory name. - (void)copy_option_part(&dirp, (char *)dir_name, 31000, ","); + (void)copy_option_part(&dirp, dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1208,24 +1208,24 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) } } else { // check directory dir_name if (fname == NULL) { - names[0] = concat_fnames((char *)dir_name, "*.sw?", true); + names[0] = concat_fnames(dir_name, "*.sw?", true); // For Unix names starting with a dot are special. MS-Windows // supports this too, on some file systems. - names[1] = concat_fnames((char *)dir_name, ".*.sw?", true); - names[2] = concat_fnames((char *)dir_name, ".sw?", true); + names[1] = concat_fnames(dir_name, ".*.sw?", true); + names[2] = concat_fnames(dir_name, ".sw?", true); num_names = 3; } else { int len = (int)STRLEN(dir_name); p = dir_name + len; - if (after_pathsep((char *)dir_name, (char *)p) + if (after_pathsep(dir_name, p) && len > 1 && p[-1] == p[-2]) { // Ends with '//', Use Full path for swap name - tail = (char_u *)make_percent_swname((char *)dir_name, + tail = (char_u *)make_percent_swname(dir_name, (char *)fname_res); } else { tail = (char_u *)path_tail((char *)fname_res); - tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, true); + tail = (char_u *)concat_fnames(dir_name, (char *)tail, true); } num_names = recov_file_names(names, (char *)tail, false); xfree(tail); @@ -1262,7 +1262,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) for (int i = 0; i < num_files; i++) { // Do not expand wildcards, on Windows would try to expand // "%tmp%" in "%tmp%file" - if (path_full_compare((char *)p, files[i], true, false) & kEqualFiles) { + if (path_full_compare(p, files[i], true, false) & kEqualFiles) { // Remove the name from files[i]. Move further entries // down. When the array becomes empty free it here, since // FreeWild() won't be called below. @@ -1292,7 +1292,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) } } else { msg_puts(_(" In directory ")); - msg_home_replace((char *)dir_name); + msg_home_replace(dir_name); msg_puts(":\n"); } @@ -3376,7 +3376,7 @@ static int b0_magic_wrong(ZERO_BL *b0p) { return b0p->b0_magic_long != B0_MAGIC_LONG || b0p->b0_magic_int != (int)B0_MAGIC_INT - || b0p->b0_magic_short != (short)B0_MAGIC_SHORT + || b0p->b0_magic_short != (int16_t)B0_MAGIC_SHORT || b0p->b0_magic_char != B0_MAGIC_CHAR; } @@ -3485,7 +3485,7 @@ static void long_to_char(long n, char_u *s) s[3] = (char_u)(n & 0xff); } -static long char_to_long(char_u *s) +static long char_to_long(const char_u *s) { long retval; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 61c43d8f99..16033e9c63 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -6,6 +6,7 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stdint.h> #include <string.h> #include "nvim/api/extmark.h" @@ -576,41 +577,48 @@ void alloc_block(Arena *arena) blk->prev = prev_blk; } +static size_t arena_align_offset(uint64_t off) +{ + return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1)); +} + /// @param arena if NULL, do a global allocation. caller must then free the value! -/// @param size if zero, will still return a non-null pointer, but not a unique one +/// @param size if zero, will still return a non-null pointer, but not a usable or unique one void *arena_alloc(Arena *arena, size_t size, bool align) { if (!arena) { return xmalloc(size); } - if (align) { - arena->pos = (arena->pos + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); + if (!arena->cur_blk) { + alloc_block(arena); } - if (arena->pos + size > arena->size || !arena->cur_blk) { + size_t alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; + if (alloc_pos + size > arena->size) { if (size > (ARENA_BLOCK_SIZE - sizeof(struct consumed_blk)) >> 1) { // if allocation is too big, allocate a large block with the requested // size, but still with block pointer head. We do this even for // arena->size / 2, as there likely is space left for the next // small allocation in the current block. - if (!arena->cur_blk) { - // to simplify free-list management, arena->cur_blk must - // always be a normal, ARENA_BLOCK_SIZE sized, block - alloc_block(arena); - } arena_alloc_count++; - char *alloc = xmalloc(size + sizeof(struct consumed_blk)); + size_t hdr_size = sizeof(struct consumed_blk); + size_t aligned_hdr_size = (align ? arena_align_offset(hdr_size) : hdr_size); + char *alloc = xmalloc(size + aligned_hdr_size); + + // to simplify free-list management, arena->cur_blk must + // always be a normal, ARENA_BLOCK_SIZE sized, block struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk; struct consumed_blk *fix_blk = (struct consumed_blk *)alloc; fix_blk->prev = cur_blk->prev; cur_blk->prev = fix_blk; - return (alloc + sizeof(struct consumed_blk)); + return alloc + aligned_hdr_size; } else { - alloc_block(arena); + alloc_block(arena); // resets arena->pos + alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; } } - char *mem = arena->cur_blk + arena->pos; - arena->pos += size; + char *mem = arena->cur_blk + alloc_pos; + arena->pos = alloc_pos + size; return mem; } diff --git a/src/nvim/memory.h b/src/nvim/memory.h index f407192331..5c3d18ac93 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -39,13 +39,13 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif -EXTERN size_t arena_alloc_count INIT(=0); +EXTERN size_t arena_alloc_count INIT(= 0); typedef struct consumed_blk { struct consumed_blk *prev; } *ArenaMem; -#define ARENA_ALIGN sizeof(void *) +#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double)) typedef struct { char *cur_blk; diff --git a/src/nvim/message.c b/src/nvim/message.c index a90e675423..fa1c8036e6 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -318,7 +318,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL - && strcmp(s, last_msg_hist->msg))) { + && strcmp(s, last_msg_hist->msg) != 0)) { add_msg_hist(s, -1, attr, multiline); } @@ -663,6 +663,13 @@ static bool emsg_multiline(const char *s, bool multiline) return true; } + if (emsg_assert_fails_used && emsg_assert_fails_msg == NULL) { + emsg_assert_fails_msg = xstrdup(s); + emsg_assert_fails_lnum = SOURCING_LNUM; + xfree(emsg_assert_fails_context); + emsg_assert_fails_context = xstrdup(SOURCING_NAME == NULL ? "" : SOURCING_NAME); + } + // set "v:errmsg", also when using ":silent! cmd" set_vim_var_string(VV_ERRMSG, s, -1); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 734ece73b4..53431187af 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -4,18 +4,26 @@ #include <stdbool.h> #include "nvim/ascii.h" +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/getchar.h" #include "nvim/grid.h" #include "nvim/memline.h" +#include "nvim/menu.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/ops.h" +#include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/plines.h" +#include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" @@ -31,10 +39,156 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Get class of a character for selection: same class means same word. +/// 0: blank +/// 1: punctuation groups +/// 2: normal word character +/// >2: multi-byte word character. +static int get_mouse_class(char_u *p) +{ + if (MB_BYTE2LEN(p[0]) > 1) { + return mb_get_class(p); + } + + const int c = *p; + if (c == ' ' || c == '\t') { + return 0; + } + if (vim_iswordc(c)) { + return 2; + } + + // There are a few special cases where we want certain combinations of + // characters to be considered as a single word. These are things like + // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each + // character is in its own class. + if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { + return 1; + } + return c; +} + +/// Move "pos" back to the start of the word it's in. +static void find_start_of_word(pos_T *pos) +{ + char_u *line; + int cclass; + int col; + + line = (char_u *)ml_get(pos->lnum); + cclass = get_mouse_class(line + pos->col); + + while (pos->col > 0) { + col = pos->col - 1; + col -= utf_head_off((char *)line, (char *)line + col); + if (get_mouse_class(line + col) != cclass) { + break; + } + pos->col = col; + } +} + +/// Move "pos" forward to the end of the word it's in. +/// When 'selection' is "exclusive", the position is just after the word. +static void find_end_of_word(pos_T *pos) +{ + char_u *line; + int cclass; + int col; + + line = (char_u *)ml_get(pos->lnum); + if (*p_sel == 'e' && pos->col > 0) { + pos->col--; + pos->col -= utf_head_off((char *)line, (char *)line + pos->col); + } + cclass = get_mouse_class(line + pos->col); + while (line[pos->col] != NUL) { + col = pos->col + utfc_ptr2len((char *)line + pos->col); + if (get_mouse_class(line + col) != cclass) { + if (*p_sel == 'e') { + pos->col = col; + } + break; + } + pos->col = col; + } +} + +/// Move the current tab to tab in same column as mouse or to end of the +/// tabline if there is no tab there. +static void move_tab_to_mouse(void) +{ + int tabnr = tab_page_click_defs[mouse_col].tabnr; + if (tabnr <= 0) { + tabpage_move(9999); + } else if (tabnr < tabpage_index(curtab)) { + tabpage_move(tabnr - 1); + } else { + tabpage_move(tabnr); + } +} + +/// Call click definition function for column "col" in the "click_defs" array for button +/// "which_button". +static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) +{ + typval_T argv[] = { + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = (varnumber_T)click_defs[col].tabnr + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK + ? 4 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK + ? 3 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK + ? 2 + : 1))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (which_button == MOUSE_LEFT + ? "l" + : (which_button == MOUSE_RIGHT + ? "r" + : (which_button == MOUSE_MIDDLE + ? "m" + : "?"))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (char[]) { + (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), + (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), + (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), + (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), + NUL + } + }, + } + }; + typval_T rettv; + (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); + tv_clear(&rettv); +} + /// Translate window coordinates to buffer position without any side effects. /// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. /// The column is one for the first column. -int get_fpos_of_mouse(pos_T *mpos) +static int get_fpos_of_mouse(pos_T *mpos) { int grid = mouse_grid; int row = mouse_row; @@ -73,6 +227,715 @@ int get_fpos_of_mouse(pos_T *mpos) return IN_BUFFER; } +/// Do the appropriate action for the current mouse click in the current mode. +/// Not used for Command-line mode. +/// +/// Normal and Visual Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes end yes +/// left press C yes end yes "^]" (2) +/// left press S yes end (popup: extend) yes "*" (2) +/// left drag - yes start if moved no +/// left relse - yes start if moved no +/// middle press - yes if not active no put register +/// middle press - yes if active no yank and put +/// right press - yes start or extend yes +/// right press S yes no change yes "#" (2) +/// right drag - yes extend no +/// right relse - yes extend no +/// +/// Insert or Replace Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes (cannot be active) yes +/// left press C yes (cannot be active) yes "CTRL-O^]" (2) +/// left press S yes (cannot be active) yes "CTRL-O*" (2) +/// left drag - yes start or extend (1) no CTRL-O (1) +/// left relse - yes start or extend (1) no CTRL-O (1) +/// middle press - no (cannot be active) no put register +/// right press - yes start or extend yes CTRL-O +/// right press S yes (cannot be active) yes "CTRL-O#" (2) +/// +/// (1) only if mouse pointer moved since press +/// (2) only if click is in same buffer +/// +/// @param oap operator argument, can be NULL +/// @param c K_LEFTMOUSE, etc +/// @param dir Direction to 'put' if necessary +/// @param fixindent PUT_FIXINDENT if fixing indent necessary +/// +/// @return true if start_arrow() should be called for edit mode. +bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) +{ + static bool got_click = false; // got a click some time back + + int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT + bool is_click; // If false it's a drag or release event + bool is_drag; // If true it's a drag event + int jump_flags = 0; // flags for jump_to_mouse() + pos_T start_visual; + bool moved; // Has cursor moved? + bool in_winbar; // mouse in window bar + bool in_status_line; // mouse in status line + static bool in_tab_line = false; // mouse clicked in tab line + bool in_sep_line; // mouse in vertical separator line + int c1, c2; + pos_T save_cursor; + win_T *old_curwin = curwin; + static pos_T orig_cursor; + colnr_T leftcol, rightcol; + pos_T end_visual; + long diff; + int old_active = VIsual_active; + int old_mode = VIsual_mode; + int regname; + + save_cursor = curwin->w_cursor; + + for (;;) { + which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); + if (is_drag) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { + int nc; + int save_mouse_grid = mouse_grid; + int save_mouse_row = mouse_row; + int save_mouse_col = mouse_col; + + // Need to get the character, peeking doesn't get the actual one. + nc = safe_vgetc(); + if (c == nc) { + continue; + } + vungetc(nc); + mouse_grid = save_mouse_grid; + mouse_row = save_mouse_row; + mouse_col = save_mouse_col; + } + } + break; + } + + if (c == K_MOUSEMOVE) { + // Mouse moved without a button pressed. + return false; + } + + // Ignore drag and release events if we didn't get a click. + if (is_click) { + got_click = true; + } else { + if (!got_click) { // didn't get click, ignore + return false; + } + if (!is_drag) { // release, reset got_click + got_click = false; + if (in_tab_line) { + in_tab_line = false; + return false; + } + } + } + + // CTRL right mouse button does CTRL-T + if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + if (count > 1) { + stuffnumReadbuff(count); + } + stuffcharReadbuff(Ctrl_T); + got_click = false; // ignore drag&release now + return false; + } + + // CTRL only works with left mouse button + if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { + return false; + } + + // When a modifier is down, ignore drag and release events, as well as + // multiple clicks and the middle mouse button. + // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". + if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT + | MOD_MASK_META)) + && (!is_click + || (mod_mask & MOD_MASK_MULTI_CLICK) + || which_button == MOUSE_MIDDLE) + && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) + && mouse_model_popup() + && which_button == MOUSE_LEFT) + && !((mod_mask & MOD_MASK_ALT) + && !mouse_model_popup() + && which_button == MOUSE_RIGHT)) { + return false; + } + + // If the button press was used as the movement command for an operator (eg + // "d<MOUSE>"), or it is the middle button that is held down, ignore + // drag/release events. + if (!is_click && which_button == MOUSE_MIDDLE) { + return false; + } + + if (oap != NULL) { + regname = oap->regname; + } else { + regname = 0; + } + + // Middle mouse button does a 'put' of the selected text + if (which_button == MOUSE_MIDDLE) { + if (State == MODE_NORMAL) { + // If an operator was pending, we don't know what the user wanted to do. + // Go back to normal mode: Clear the operator and beep(). + if (oap != NULL && oap->op_type != OP_NOP) { + clearopbeep(oap); + return false; + } + + // If visual was active, yank the highlighted text and put it + // before the mouse pointer position. + // In Select mode replace the highlighted text with the clipboard. + if (VIsual_active) { + if (VIsual_select) { + stuffcharReadbuff(Ctrl_G); + stuffReadbuff("\"+p"); + } else { + stuffcharReadbuff('y'); + stuffcharReadbuff(K_MIDDLEMOUSE); + } + return false; + } + // The rest is below jump_to_mouse() + } else if ((State & MODE_INSERT) == 0) { + return false; + } + + // Middle click in insert mode doesn't move the mouse, just insert the + // contents of a register. '.' register is special, can't insert that + // with do_put(). + // Also paste at the cursor if the current mode isn't in 'mouse' (only + // happens for the GUI). + if ((State & MODE_INSERT)) { + if (regname == '.') { + insert_reg(regname, true); + } else { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { + insert_reg(regname, true); + } else { + do_put(regname, NULL, BACKWARD, 1L, + (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); + + // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r + AppendCharToRedobuff(Ctrl_R); + AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); + AppendCharToRedobuff(regname == 0 ? '"' : regname); + } + } + return false; + } + } + + // When dragging or button-up stay in the same window. + if (!is_click) { + jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; + } + + start_visual.lnum = 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(); + } + return false; + } + + // click in a tab selects that tab page + if (is_click && cmdwin_type == 0 && mouse_col < Columns) { + in_tab_line = true; + c1 = tab_page_click_defs[mouse_col].tabnr; + switch (tab_page_click_defs[mouse_col].type) { + case kStlClickDisabled: + break; + case kStlClickTabClose: { + tabpage_T *tp; + + // Close the current or specified tab page. + if (c1 == 999) { + tp = curtab; + } else { + tp = find_tabpage(c1); + } + if (tp == curtab) { + if (first_tabpage->tp_next != NULL) { + tabpage_close(false); + } + } else if (tp != NULL) { + tabpage_close_other(tp, false); + } + break; + } + case kStlClickTabSwitch: + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // double click opens new page + end_visual_mode(); + tabpage_new(); + tabpage_move(c1 == 0 ? 9999 : c1 - 1); + } else { + // Go to specified tab page, or next one if not clicking + // on a label. + goto_tabpage(c1); + + // It's like clicking on the status line of a window. + if (curwin != old_curwin) { + end_visual_mode(); + } + } + break; + case kStlClickFuncRun: + call_click_def_func(tab_page_click_defs, mouse_col, which_button); + break; + } + } + return true; + } else if (is_drag && in_tab_line) { + move_tab_to_mouse(); + return false; + } + + // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: + // right button up -> pop-up menu + // shift-left button -> right button + // alt-left button -> alt-right button + if (mouse_model_popup()) { + if (which_button == MOUSE_RIGHT + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (!is_click) { + // Ignore right button release events, only shows the popup + // menu on the button down event. + return false; + } + jump_flags = 0; + if (strcmp(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup + // menu. + if (VIsual_active) { + pos_T m_pos; + // set MOUSE_MAY_STOP_VIS if we are outside the + // selection or the current window (might have false + // negative here) + if (mouse_row < curwin->w_winrow + || mouse_row > (curwin->w_winrow + curwin->w_height)) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if (VIsual_mode == 'V') { + if ((curwin->w_cursor.lnum <= VIsual.lnum + && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) + || (VIsual.lnum < curwin->w_cursor.lnum + && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } else if ((ltoreq(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); + update_screen(); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return (jump_flags & CURSOR_MOVED) != 0; + } + if (which_button == MOUSE_LEFT + && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { + which_button = MOUSE_RIGHT; + mod_mask &= ~MOD_MASK_SHIFT; + } + } + + if ((State & (MODE_NORMAL | MODE_INSERT)) + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (which_button == MOUSE_LEFT) { + if (is_click) { + // stop Visual mode for a left click in a window, but not when on a status line + if (VIsual_active) { + jump_flags |= MOUSE_MAY_STOP_VIS; + } + } else { + jump_flags |= MOUSE_MAY_VIS; + } + } else if (which_button == MOUSE_RIGHT) { + if (is_click && VIsual_active) { + // Remember the start and end of visual before moving the cursor. + if (lt(curwin->w_cursor, VIsual)) { + start_visual = curwin->w_cursor; + end_visual = VIsual; + } else { + start_visual = VIsual; + end_visual = curwin->w_cursor; + } + } + jump_flags |= MOUSE_FOCUS; + jump_flags |= MOUSE_MAY_VIS; + } + } + + // If an operator is pending, ignore all drags and releases until the next mouse click. + if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { + got_click = false; + oap->motion_type = kMTCharWise; + } + + // When releasing the button let jump_to_mouse() know. + if (!is_click && !is_drag) { + jump_flags |= MOUSE_RELEASED; + } + + // JUMP! + jump_flags = jump_to_mouse(jump_flags, + oap == NULL ? NULL : &(oap->inclusive), + which_button); + + moved = (jump_flags & CURSOR_MOVED); + in_winbar = (jump_flags & MOUSE_WINBAR); + in_status_line = (jump_flags & IN_STATUS_LINE); + in_sep_line = (jump_flags & IN_SEP_LINE); + + if ((in_winbar || in_status_line) && is_click) { + // Handle click event on window bar or status lin + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; + win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); + if (wp == NULL) { + return false; + } + + StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs + : wp->w_winbar_click_defs; + + if (in_status_line && global_stl_height() > 0) { + // global statusline is displayed for the current window, + // and spans the whole screen. + click_defs = curwin->w_status_click_defs; + click_col = mouse_col; + } + + if (click_defs != NULL) { + switch (click_defs[click_col].type) { + case kStlClickDisabled: + break; + case kStlClickFuncRun: + call_click_def_func(click_defs, click_col, which_button); + break; + default: + assert(false && "winbar and statusline only support %@ for clicks"); + break; + } + } + + return false; + } else if (in_winbar) { + // A drag or release event in the window bar has no side effects. + return false; + } + + // When jumping to another window, clear a pending operator. That's a bit + // friendlier than beeping and not jumping to that window. + if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { + clearop(oap); + } + + if (mod_mask == 0 + && !is_drag + && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) + && which_button == MOUSE_LEFT) { + // open or close a fold at this line + if (jump_flags & MOUSE_FOLD_OPEN) { + openFold(curwin->w_cursor, 1L); + } else { + closeFold(curwin->w_cursor, 1L); + } + // don't move the cursor if still in the same window + if (curwin == old_curwin) { + curwin->w_cursor = save_cursor; + } + } + + // Set global flag that we are extending the Visual area with mouse dragging; + // temporarily minimize 'scrolloff'. + if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { + // In the very first line, allow scrolling one line + if (mouse_row == 0) { + mouse_dragging = 2; + } else { + mouse_dragging = 1; + } + } + + // When dragging the mouse above the window, scroll down. + if (is_drag && mouse_row < 0 && !in_status_line) { + scroll_redraw(false, 1L); + mouse_row = 0; + } + + if (start_visual.lnum) { // right click in visual mode + // When ALT is pressed make Visual mode blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } + + // In Visual-block mode, divide the area in four, pick up the corner + // that is in the quarter that the cursor is in. + if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); + if (curwin->w_curswant > (leftcol + rightcol) / 2) { + end_visual.col = leftcol; + } else { + end_visual.col = rightcol; + } + if (curwin->w_cursor.lnum >= + (start_visual.lnum + end_visual.lnum) / 2) { + end_visual.lnum = start_visual.lnum; + } + + // move VIsual to the right column + start_visual = curwin->w_cursor; // save the cursor pos + curwin->w_cursor = end_visual; + coladvance(end_visual.col); + VIsual = curwin->w_cursor; + curwin->w_cursor = start_visual; // restore the cursor + } else { + // If the click is before the start of visual, change the start. + // If the click is after the end of visual, change the end. If + // the click is inside the visual, change the closest side. + if (lt(curwin->w_cursor, start_visual)) { + VIsual = end_visual; + } else if (lt(end_visual, curwin->w_cursor)) { + VIsual = start_visual; + } else { + // In the same line, compare column number + if (end_visual.lnum == start_visual.lnum) { + if (curwin->w_cursor.col - start_visual.col > + end_visual.col - curwin->w_cursor.col) { + VIsual = start_visual; + } else { + VIsual = end_visual; + } + } else { + // In different lines, compare line number + diff = (curwin->w_cursor.lnum - start_visual.lnum) - + (end_visual.lnum - curwin->w_cursor.lnum); + + if (diff > 0) { // closest to end + VIsual = start_visual; + } else if (diff < 0) { // closest to start + VIsual = end_visual; + } else { // in the middle line + if (curwin->w_cursor.col < + (start_visual.col + end_visual.col) / 2) { + VIsual = end_visual; + } else { + VIsual = start_visual; + } + } + } + } + } + } else if ((State & MODE_INSERT) && VIsual_active) { + // If Visual mode started in insert mode, execute "CTRL-O" + stuffcharReadbuff(Ctrl_O); + } + + // Middle mouse click: Put text before cursor. + if (which_button == MOUSE_MIDDLE) { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if (yank_register_mline(regname)) { + if (mouse_past_bottom) { + dir = FORWARD; + } + } else if (mouse_past_eol) { + dir = FORWARD; + } + + if (fixindent) { + c1 = (dir == BACKWARD) ? '[' : ']'; + c2 = 'p'; + } else { + c1 = (dir == FORWARD) ? 'p' : 'P'; + c2 = NUL; + } + prep_redo(regname, count, NUL, c1, NUL, c2, NUL); + + // Remember where the paste started, so in edit() Insstart can be set to this position + if (restart_edit != 0) { + where_paste_started = curwin->w_cursor; + } + do_put(regname, NULL, dir, count, + (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); + } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + && bt_quickfix(curbuf)) { + // Ctrl-Mouse click or double click in a quickfix window jumps to the + // error under the mouse pointer. + if (curwin->w_llist_ref == NULL) { // quickfix window + do_cmdline_cmd(".cc"); + } else { // location list window + do_cmdline_cmd(".ll"); + } + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_CTRL) + || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { + // Ctrl-Mouse click (or double click in a help window) jumps to the tag + // under the mouse pointer. + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + stuffcharReadbuff(Ctrl_RSB); + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_SHIFT)) { + // Shift-Mouse click searches for the next occurrence of the word under + // the mouse pointer + if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { + stuffcharReadbuff(Ctrl_O); + } + if (which_button == MOUSE_LEFT) { + stuffcharReadbuff('*'); + } else { // MOUSE_RIGHT + stuffcharReadbuff('#'); + } + } else if (in_status_line || in_sep_line) { + // Do nothing if on status line or vertical separator + // Handle double clicks otherwise + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { + if (is_click || !VIsual_active) { + if (VIsual_active) { + orig_cursor = VIsual; + } else { + VIsual = curwin->w_cursor; + orig_cursor = VIsual; + VIsual_active = true; + VIsual_reselect = true; + // start Select mode if 'selectmode' contains "mouse" + may_start_select('o'); + setmouse(); + } + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // Double click with ALT pressed makes it blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { + VIsual_mode = 'V'; + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { + VIsual_mode = Ctrl_V; + } + } + // A double click selects a word or a block. + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + pos_T *pos = NULL; + int gc; + + if (is_click) { + // If the character under the cursor (skipping white space) is + // not a word character, try finding a match and select a (), + // {}, [], #if/#endif, etc. block. + end_visual = curwin->w_cursor; + while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { + inc(&end_visual); + } + if (oap != NULL) { + oap->motion_type = kMTCharWise; + } + if (oap != NULL + && VIsual_mode == 'v' + && !vim_iswordc(gchar_pos(&end_visual)) + && equalpos(curwin->w_cursor, VIsual) + && (pos = findmatch(oap, NUL)) != NULL) { + curwin->w_cursor = *pos; + if (oap->motion_type == kMTLineWise) { + VIsual_mode = 'V'; + } else if (*p_sel == 'e') { + if (lt(curwin->w_cursor, VIsual)) { + VIsual.col++; + } else { + curwin->w_cursor.col++; + } + } + } + } + + if (pos == NULL && (is_click || is_drag)) { + // When not found a match or when dragging: extend to include a word. + if (lt(curwin->w_cursor, orig_cursor)) { + find_start_of_word(&curwin->w_cursor); + find_end_of_word(&VIsual); + } else { + find_start_of_word(&VIsual); + if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { + curwin->w_cursor.col += + utfc_ptr2len(get_cursor_pos_ptr()); + } + find_end_of_word(&curwin->w_cursor); + } + } + curwin->w_set_curswant = true; + } + if (is_click) { + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } + } else if (VIsual_active && !old_active) { + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } + + // If Visual mode changed show it later. + if ((!VIsual_active && old_active && mode_displayed) + || (VIsual_active && p_smd && msg_silent == 0 + && (!old_active || VIsual_mode != old_mode))) { + redraw_cmdline = true; + } + + return moved; +} + /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { @@ -99,6 +962,21 @@ bool is_mouse_key(int c) || c == K_X2DRAG || c == K_X2RELEASE; } + +/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". +static bool mouse_model_popup(void) +{ + return p_mousem[0] == 'p'; +} + +static win_T *dragwin = NULL; ///< window being dragged + +/// Reset the window being dragged. To be called when switching tab page. +void reset_dragwin(void) +{ + dragwin = NULL; +} + /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. @@ -135,7 +1013,6 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) static bool on_winbar = false; static int prev_row = -1; static int prev_col = -1; - static win_T *dragwin = NULL; // window being dragged static int did_drag = false; // drag was noticed win_T *wp, *old_curwin; @@ -653,7 +1530,7 @@ void setmouse(void) // Set orig_topline. Used when jumping to another window, so that a double // click still works. -void set_mouse_topline(win_T *wp) +static void set_mouse_topline(win_T *wp) { orig_topline = wp->w_topline; orig_topfill = wp->w_topfill; diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 21ff56bbbc..d493a479b1 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -4,42 +4,53 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/normal.h" #include "nvim/vim.h" - -// jump_to_mouse() returns one of first four these values, possibly with -// some of the other three added. -#define IN_UNKNOWN 0 -#define IN_BUFFER 1 -#define IN_STATUS_LINE 2 // on status or command line -#define IN_SEP_LINE 4 // on vertical separator line -#define IN_OTHER_WIN 8 // in other window but can't go there -#define CURSOR_MOVED 0x100 -#define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column -#define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column -#define MOUSE_WINBAR 0x800 // in window toolbar - -// flags for jump_to_mouse() -#define MOUSE_FOCUS 0x01 // need to stay in this window -#define MOUSE_MAY_VIS 0x02 // may start Visual mode -#define MOUSE_DID_MOVE 0x04 // only act when mouse has moved -#define MOUSE_SETPOS 0x08 // only set current mouse position -#define MOUSE_MAY_STOP_VIS 0x10 // may stop Visual mode -#define MOUSE_RELEASED 0x20 // button was released - -// Codes for mouse button events in lower three bits: -#define MOUSE_LEFT 0x00 -#define MOUSE_MIDDLE 0x01 -#define MOUSE_RIGHT 0x02 -#define MOUSE_RELEASE 0x03 - -#define MOUSE_X1 0x300 // Mouse-button X1 (6th) -#define MOUSE_X2 0x400 // Mouse-button X2 - -// Direction for nv_mousescroll() and ins_mousescroll() -#define MSCR_DOWN 0 // DOWN must be false -#define MSCR_UP 1 -#define MSCR_LEFT (-1) -#define MSCR_RIGHT (-2) +#include "nvim/window.h" + +/// jump_to_mouse() returns one of first five these values, possibly with +/// some of the other four added. +enum { + IN_UNKNOWN = 0, + IN_BUFFER = 1, + IN_STATUS_LINE = 2, ///< on status or command line + IN_SEP_LINE = 4, ///< on vertical separator line + IN_OTHER_WIN = 8, ///< in other window but can't go there + CURSOR_MOVED = 0x100, + MOUSE_FOLD_CLOSE = 0x200, ///< clicked on '-' in fold column + MOUSE_FOLD_OPEN = 0x400, ///< clicked on '+' in fold column + MOUSE_WINBAR = 0x800, ///< in window toolbar +}; + +/// flags for jump_to_mouse() +enum { + MOUSE_FOCUS = 0x01, ///< need to stay in this window + MOUSE_MAY_VIS = 0x02, ///< may start Visual mode + MOUSE_DID_MOVE = 0x04, ///< only act when mouse has moved + MOUSE_SETPOS = 0x08, ///< only set current mouse position + MOUSE_MAY_STOP_VIS = 0x10, ///< may stop Visual mode + MOUSE_RELEASED = 0x20, ///< button was released +}; + +enum { + // Codes for mouse button events in lower three bits: + MOUSE_LEFT = 0x00, + MOUSE_MIDDLE = 0x01, + MOUSE_RIGHT = 0x02, + MOUSE_RELEASE = 0x03, + + // mouse buttons that are handled like a key press + MOUSE_X1 = 0x300, ///< Mouse-button X1 (6th) + MOUSE_X2 = 0x400, ///< Mouse-button X2 +}; + +/// Direction for nv_mousescroll() and ins_mousescroll() +enum { + MSCR_DOWN = 0, ///< DOWN must be false + MSCR_UP = 1, + MSCR_LEFT = -1, + MSCR_RIGHT = -2, +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.h.generated.h" diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b7febe2d51..d142af555a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -45,7 +45,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -145,8 +144,7 @@ static const struct nv_cmd { nv_func_T cmd_func; ///< function for this command uint16_t cmd_flags; ///< NV_ flags int16_t cmd_arg; ///< value for ca.arg -} nv_cmds[] = -{ +} nv_cmds[] = { { NUL, nv_error, 0, 0 }, { Ctrl_A, nv_addsub, 0, 0 }, { Ctrl_B, nv_page, NV_STS, BACKWARD }, @@ -1434,861 +1432,6 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; // only set v:prevcount once } -/// Move the current tab to tab in same column as mouse or to end of the -/// tabline if there is no tab there. -static void move_tab_to_mouse(void) -{ - int tabnr = tab_page_click_defs[mouse_col].tabnr; - if (tabnr <= 0) { - tabpage_move(9999); - } else if (tabnr < tabpage_index(curtab)) { - tabpage_move(tabnr - 1); - } else { - tabpage_move(tabnr); - } -} - -/// Call click definition function for column "col" in the "click_defs" array for button -/// "which_button". -static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) -{ - typval_T argv[] = { - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = (varnumber_T)click_defs[col].tabnr - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK - ? 4 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK - ? 3 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK - ? 2 - : 1))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (which_button == MOUSE_LEFT - ? "l" - : (which_button == MOUSE_RIGHT - ? "r" - : (which_button == MOUSE_MIDDLE - ? "m" - : "?"))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (char[]) { - (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), - (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), - (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), - (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), - NUL - } - }, - } - }; - typval_T rettv; - (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); - tv_clear(&rettv); -} - -/// Do the appropriate action for the current mouse click in the current mode. -/// Not used for Command-line mode. -/// -/// Normal and Visual Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes end yes -/// left press C yes end yes "^]" (2) -/// left press S yes end (popup: extend) yes "*" (2) -/// left drag - yes start if moved no -/// left relse - yes start if moved no -/// middle press - yes if not active no put register -/// middle press - yes if active no yank and put -/// right press - yes start or extend yes -/// right press S yes no change yes "#" (2) -/// right drag - yes extend no -/// right relse - yes extend no -/// -/// Insert or Replace Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes (cannot be active) yes -/// left press C yes (cannot be active) yes "CTRL-O^]" (2) -/// left press S yes (cannot be active) yes "CTRL-O*" (2) -/// left drag - yes start or extend (1) no CTRL-O (1) -/// left relse - yes start or extend (1) no CTRL-O (1) -/// middle press - no (cannot be active) no put register -/// right press - yes start or extend yes CTRL-O -/// right press S yes (cannot be active) yes "CTRL-O#" (2) -/// -/// (1) only if mouse pointer moved since press -/// (2) only if click is in same buffer -/// -/// @param oap operator argument, can be NULL -/// @param c K_LEFTMOUSE, etc -/// @param dir Direction to 'put' if necessary -/// @param fixindent PUT_FIXINDENT if fixing indent necessary -/// -/// @return true if start_arrow() should be called for edit mode. -bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) -{ - static bool got_click = false; // got a click some time back - - int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT - bool is_click; // If false it's a drag or release event - bool is_drag; // If true it's a drag event - int jump_flags = 0; // flags for jump_to_mouse() - pos_T start_visual; - bool moved; // Has cursor moved? - bool in_winbar; // mouse in window bar - bool in_status_line; // mouse in status line - static bool in_tab_line = false; // mouse clicked in tab line - bool in_sep_line; // mouse in vertical separator line - int c1, c2; - pos_T save_cursor; - win_T *old_curwin = curwin; - static pos_T orig_cursor; - colnr_T leftcol, rightcol; - pos_T end_visual; - long diff; - int old_active = VIsual_active; - int old_mode = VIsual_mode; - int regname; - - save_cursor = curwin->w_cursor; - - for (;;) { - which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); - if (is_drag) { - // If the next character is the same mouse event then use that - // one. Speeds up dragging the status line. - // Note: Since characters added to the stuff buffer in the code - // below need to come before the next character, do not do this - // when the current character was stuffed. - if (!KeyStuffed && vpeekc() != NUL) { - int nc; - int save_mouse_grid = mouse_grid; - int save_mouse_row = mouse_row; - int save_mouse_col = mouse_col; - - // Need to get the character, peeking doesn't get the actual one. - nc = safe_vgetc(); - if (c == nc) { - continue; - } - vungetc(nc); - mouse_grid = save_mouse_grid; - mouse_row = save_mouse_row; - mouse_col = save_mouse_col; - } - } - break; - } - - if (c == K_MOUSEMOVE) { - // Mouse moved without a button pressed. - return false; - } - - // Ignore drag and release events if we didn't get a click. - if (is_click) { - got_click = true; - } else { - if (!got_click) { // didn't get click, ignore - return false; - } - if (!is_drag) { // release, reset got_click - got_click = false; - if (in_tab_line) { - in_tab_line = false; - return false; - } - } - } - - // CTRL right mouse button does CTRL-T - if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - if (count > 1) { - stuffnumReadbuff(count); - } - stuffcharReadbuff(Ctrl_T); - got_click = false; // ignore drag&release now - return false; - } - - // CTRL only works with left mouse button - if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { - return false; - } - - // When a modifier is down, ignore drag and release events, as well as - // multiple clicks and the middle mouse button. - // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". - if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT - | MOD_MASK_META)) - && (!is_click - || (mod_mask & MOD_MASK_MULTI_CLICK) - || which_button == MOUSE_MIDDLE) - && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) - && mouse_model_popup() - && which_button == MOUSE_LEFT) - && !((mod_mask & MOD_MASK_ALT) - && !mouse_model_popup() - && which_button == MOUSE_RIGHT)) { - return false; - } - - // If the button press was used as the movement command for an operator (eg - // "d<MOUSE>"), or it is the middle button that is held down, ignore - // drag/release events. - if (!is_click && which_button == MOUSE_MIDDLE) { - return false; - } - - if (oap != NULL) { - regname = oap->regname; - } else { - regname = 0; - } - - // Middle mouse button does a 'put' of the selected text - if (which_button == MOUSE_MIDDLE) { - if (State == MODE_NORMAL) { - // If an operator was pending, we don't know what the user wanted to do. - // Go back to normal mode: Clear the operator and beep(). - if (oap != NULL && oap->op_type != OP_NOP) { - clearopbeep(oap); - return false; - } - - // If visual was active, yank the highlighted text and put it - // before the mouse pointer position. - // In Select mode replace the highlighted text with the clipboard. - if (VIsual_active) { - if (VIsual_select) { - stuffcharReadbuff(Ctrl_G); - stuffReadbuff("\"+p"); - } else { - stuffcharReadbuff('y'); - stuffcharReadbuff(K_MIDDLEMOUSE); - } - return false; - } - // The rest is below jump_to_mouse() - } else if ((State & MODE_INSERT) == 0) { - return false; - } - - // Middle click in insert mode doesn't move the mouse, just insert the - // contents of a register. '.' register is special, can't insert that - // with do_put(). - // Also paste at the cursor if the current mode isn't in 'mouse' (only - // happens for the GUI). - if ((State & MODE_INSERT)) { - if (regname == '.') { - insert_reg(regname, true); - } else { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { - insert_reg(regname, true); - } else { - do_put(regname, NULL, BACKWARD, 1L, - (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); - - // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r - AppendCharToRedobuff(Ctrl_R); - AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); - AppendCharToRedobuff(regname == 0 ? '"' : regname); - } - } - return false; - } - } - - // When dragging or button-up stay in the same window. - if (!is_click) { - jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; - } - - start_visual.lnum = 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(); - } - return false; - } - - // click in a tab selects that tab page - if (is_click && cmdwin_type == 0 && mouse_col < Columns) { - in_tab_line = true; - c1 = tab_page_click_defs[mouse_col].tabnr; - switch (tab_page_click_defs[mouse_col].type) { - case kStlClickDisabled: - break; - case kStlClickTabClose: { - tabpage_T *tp; - - // Close the current or specified tab page. - if (c1 == 999) { - tp = curtab; - } else { - tp = find_tabpage(c1); - } - if (tp == curtab) { - if (first_tabpage->tp_next != NULL) { - tabpage_close(false); - } - } else if (tp != NULL) { - tabpage_close_other(tp, false); - } - break; - } - case kStlClickTabSwitch: - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // double click opens new page - end_visual_mode(); - tabpage_new(); - tabpage_move(c1 == 0 ? 9999 : c1 - 1); - } else { - // Go to specified tab page, or next one if not clicking - // on a label. - goto_tabpage(c1); - - // It's like clicking on the status line of a window. - if (curwin != old_curwin) { - end_visual_mode(); - } - } - break; - case kStlClickFuncRun: - call_click_def_func(tab_page_click_defs, mouse_col, which_button); - break; - } - } - return true; - } else if (is_drag && in_tab_line) { - move_tab_to_mouse(); - return false; - } - - // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: - // right button up -> pop-up menu - // shift-left button -> right button - // alt-left button -> alt-right button - if (mouse_model_popup()) { - if (which_button == MOUSE_RIGHT - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (!is_click) { - // Ignore right button release events, only shows the popup - // menu on the button down event. - return false; - } - jump_flags = 0; - if (strcmp(p_mousem, "popup_setpos") == 0) { - // First set the cursor position before showing the popup - // menu. - if (VIsual_active) { - pos_T m_pos; - // set MOUSE_MAY_STOP_VIS if we are outside the - // selection or the current window (might have false - // negative here) - if (mouse_row < curwin->w_winrow - || mouse_row > (curwin->w_winrow + curwin->w_height)) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else { - if (VIsual_mode == 'V') { - if ((curwin->w_cursor.lnum <= VIsual.lnum - && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) - || (VIsual.lnum < curwin->w_cursor.lnum - && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } else if ((ltoreq(curwin->w_cursor, VIsual) - && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) - || (lt(VIsual, curwin->w_cursor) - && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); - getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); - if (m_pos.col < leftcol || m_pos.col > rightcol) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - } - } else { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - if (jump_flags) { - jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); - update_screen(); - setcursor(); - ui_flush(); // Update before showing popup menu - } - show_popupmenu(); - got_click = false; // ignore release events - return (jump_flags & CURSOR_MOVED) != 0; - } - if (which_button == MOUSE_LEFT - && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { - which_button = MOUSE_RIGHT; - mod_mask &= ~MOD_MASK_SHIFT; - } - } - - if ((State & (MODE_NORMAL | MODE_INSERT)) - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (which_button == MOUSE_LEFT) { - if (is_click) { - // stop Visual mode for a left click in a window, but not when on a status line - if (VIsual_active) { - jump_flags |= MOUSE_MAY_STOP_VIS; - } - } else { - jump_flags |= MOUSE_MAY_VIS; - } - } else if (which_button == MOUSE_RIGHT) { - if (is_click && VIsual_active) { - // Remember the start and end of visual before moving the cursor. - if (lt(curwin->w_cursor, VIsual)) { - start_visual = curwin->w_cursor; - end_visual = VIsual; - } else { - start_visual = VIsual; - end_visual = curwin->w_cursor; - } - } - jump_flags |= MOUSE_FOCUS; - jump_flags |= MOUSE_MAY_VIS; - } - } - - // If an operator is pending, ignore all drags and releases until the next mouse click. - if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { - got_click = false; - oap->motion_type = kMTCharWise; - } - - // When releasing the button let jump_to_mouse() know. - if (!is_click && !is_drag) { - jump_flags |= MOUSE_RELEASED; - } - - // JUMP! - jump_flags = jump_to_mouse(jump_flags, - oap == NULL ? NULL : &(oap->inclusive), - which_button); - - moved = (jump_flags & CURSOR_MOVED); - in_winbar = (jump_flags & MOUSE_WINBAR); - in_status_line = (jump_flags & IN_STATUS_LINE); - in_sep_line = (jump_flags & IN_SEP_LINE); - - if ((in_winbar || in_status_line) && is_click) { - // Handle click event on window bar or status lin - int click_grid = mouse_grid; - int click_row = mouse_row; - int click_col = mouse_col; - win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp == NULL) { - return false; - } - - StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs - : wp->w_winbar_click_defs; - - if (in_status_line && global_stl_height() > 0) { - // global statusline is displayed for the current window, - // and spans the whole screen. - click_defs = curwin->w_status_click_defs; - click_col = mouse_col; - } - - if (click_defs != NULL) { - switch (click_defs[click_col].type) { - case kStlClickDisabled: - break; - case kStlClickFuncRun: - call_click_def_func(click_defs, click_col, which_button); - break; - default: - assert(false && "winbar and statusline only support %@ for clicks"); - break; - } - } - - return false; - } else if (in_winbar) { - // A drag or release event in the window bar has no side effects. - return false; - } - - // When jumping to another window, clear a pending operator. That's a bit - // friendlier than beeping and not jumping to that window. - if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { - clearop(oap); - } - - if (mod_mask == 0 - && !is_drag - && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) - && which_button == MOUSE_LEFT) { - // open or close a fold at this line - if (jump_flags & MOUSE_FOLD_OPEN) { - openFold(curwin->w_cursor, 1L); - } else { - closeFold(curwin->w_cursor, 1L); - } - // don't move the cursor if still in the same window - if (curwin == old_curwin) { - curwin->w_cursor = save_cursor; - } - } - - // Set global flag that we are extending the Visual area with mouse dragging; - // temporarily minimize 'scrolloff'. - if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { - // In the very first line, allow scrolling one line - if (mouse_row == 0) { - mouse_dragging = 2; - } else { - mouse_dragging = 1; - } - } - - // When dragging the mouse above the window, scroll down. - if (is_drag && mouse_row < 0 && !in_status_line) { - scroll_redraw(false, 1L); - mouse_row = 0; - } - - if (start_visual.lnum) { // right click in visual mode - // When ALT is pressed make Visual mode blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } - - // In Visual-block mode, divide the area in four, pick up the corner - // that is in the quarter that the cursor is in. - if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); - if (curwin->w_curswant > (leftcol + rightcol) / 2) { - end_visual.col = leftcol; - } else { - end_visual.col = rightcol; - } - if (curwin->w_cursor.lnum >= - (start_visual.lnum + end_visual.lnum) / 2) { - end_visual.lnum = start_visual.lnum; - } - - // move VIsual to the right column - start_visual = curwin->w_cursor; // save the cursor pos - curwin->w_cursor = end_visual; - coladvance(end_visual.col); - VIsual = curwin->w_cursor; - curwin->w_cursor = start_visual; // restore the cursor - } else { - // If the click is before the start of visual, change the start. - // If the click is after the end of visual, change the end. If - // the click is inside the visual, change the closest side. - if (lt(curwin->w_cursor, start_visual)) { - VIsual = end_visual; - } else if (lt(end_visual, curwin->w_cursor)) { - VIsual = start_visual; - } else { - // In the same line, compare column number - if (end_visual.lnum == start_visual.lnum) { - if (curwin->w_cursor.col - start_visual.col > - end_visual.col - curwin->w_cursor.col) { - VIsual = start_visual; - } else { - VIsual = end_visual; - } - } else { - // In different lines, compare line number - diff = (curwin->w_cursor.lnum - start_visual.lnum) - - (end_visual.lnum - curwin->w_cursor.lnum); - - if (diff > 0) { // closest to end - VIsual = start_visual; - } else if (diff < 0) { // closest to start - VIsual = end_visual; - } else { // in the middle line - if (curwin->w_cursor.col < - (start_visual.col + end_visual.col) / 2) { - VIsual = end_visual; - } else { - VIsual = start_visual; - } - } - } - } - } - } else if ((State & MODE_INSERT) && VIsual_active) { - // If Visual mode started in insert mode, execute "CTRL-O" - stuffcharReadbuff(Ctrl_O); - } - - // Middle mouse click: Put text before cursor. - if (which_button == MOUSE_MIDDLE) { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if (yank_register_mline(regname)) { - if (mouse_past_bottom) { - dir = FORWARD; - } - } else if (mouse_past_eol) { - dir = FORWARD; - } - - if (fixindent) { - c1 = (dir == BACKWARD) ? '[' : ']'; - c2 = 'p'; - } else { - c1 = (dir == FORWARD) ? 'p' : 'P'; - c2 = NUL; - } - prep_redo(regname, count, NUL, c1, NUL, c2, NUL); - - // Remember where the paste started, so in edit() Insstart can be set to this position - if (restart_edit != 0) { - where_paste_started = curwin->w_cursor; - } - do_put(regname, NULL, dir, count, - (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); - } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) - && bt_quickfix(curbuf)) { - // Ctrl-Mouse click or double click in a quickfix window jumps to the - // error under the mouse pointer. - if (curwin->w_llist_ref == NULL) { // quickfix window - do_cmdline_cmd(".cc"); - } else { // location list window - do_cmdline_cmd(".ll"); - } - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_CTRL) - || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { - // Ctrl-Mouse click (or double click in a help window) jumps to the tag - // under the mouse pointer. - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - stuffcharReadbuff(Ctrl_RSB); - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_SHIFT)) { - // Shift-Mouse click searches for the next occurrence of the word under - // the mouse pointer - if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { - stuffcharReadbuff(Ctrl_O); - } - if (which_button == MOUSE_LEFT) { - stuffcharReadbuff('*'); - } else { // MOUSE_RIGHT - stuffcharReadbuff('#'); - } - } else if (in_status_line || in_sep_line) { - // Do nothing if on status line or vertical separator - // Handle double clicks otherwise - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { - if (is_click || !VIsual_active) { - if (VIsual_active) { - orig_cursor = VIsual; - } else { - VIsual = curwin->w_cursor; - orig_cursor = VIsual; - VIsual_active = true; - VIsual_reselect = true; - // start Select mode if 'selectmode' contains "mouse" - may_start_select('o'); - setmouse(); - } - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // Double click with ALT pressed makes it blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { - VIsual_mode = 'V'; - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { - VIsual_mode = Ctrl_V; - } - } - // A double click selects a word or a block. - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - pos_T *pos = NULL; - int gc; - - if (is_click) { - // If the character under the cursor (skipping white space) is - // not a word character, try finding a match and select a (), - // {}, [], #if/#endif, etc. block. - end_visual = curwin->w_cursor; - while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { - inc(&end_visual); - } - if (oap != NULL) { - oap->motion_type = kMTCharWise; - } - if (oap != NULL - && VIsual_mode == 'v' - && !vim_iswordc(gchar_pos(&end_visual)) - && equalpos(curwin->w_cursor, VIsual) - && (pos = findmatch(oap, NUL)) != NULL) { - curwin->w_cursor = *pos; - if (oap->motion_type == kMTLineWise) { - VIsual_mode = 'V'; - } else if (*p_sel == 'e') { - if (lt(curwin->w_cursor, VIsual)) { - VIsual.col++; - } else { - curwin->w_cursor.col++; - } - } - } - } - - if (pos == NULL && (is_click || is_drag)) { - // When not found a match or when dragging: extend to include a word. - if (lt(curwin->w_cursor, orig_cursor)) { - find_start_of_word(&curwin->w_cursor); - find_end_of_word(&VIsual); - } else { - find_start_of_word(&VIsual); - if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { - curwin->w_cursor.col += - utfc_ptr2len(get_cursor_pos_ptr()); - } - find_end_of_word(&curwin->w_cursor); - } - } - curwin->w_set_curswant = true; - } - if (is_click) { - redraw_curbuf_later(UPD_INVERTED); // update the inversion - } - } else if (VIsual_active && !old_active) { - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } - - // If Visual mode changed show it later. - if ((!VIsual_active && old_active && mode_displayed) - || (VIsual_active && p_smd && msg_silent == 0 - && (!old_active || VIsual_mode != old_mode))) { - redraw_cmdline = true; - } - - return moved; -} - -/// Move "pos" back to the start of the word it's in. -static void find_start_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = (char_u *)ml_get(pos->lnum); - cclass = get_mouse_class(line + pos->col); - - while (pos->col > 0) { - col = pos->col - 1; - col -= utf_head_off((char *)line, (char *)line + col); - if (get_mouse_class(line + col) != cclass) { - break; - } - pos->col = col; - } -} - -/// Move "pos" forward to the end of the word it's in. -/// When 'selection' is "exclusive", the position is just after the word. -static void find_end_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = (char_u *)ml_get(pos->lnum); - if (*p_sel == 'e' && pos->col > 0) { - pos->col--; - pos->col -= utf_head_off((char *)line, (char *)line + pos->col); - } - cclass = get_mouse_class(line + pos->col); - while (line[pos->col] != NUL) { - col = pos->col + utfc_ptr2len((char *)line + pos->col); - if (get_mouse_class(line + col) != cclass) { - if (*p_sel == 'e') { - pos->col = col; - } - break; - } - pos->col = col; - } -} - -/// Get class of a character for selection: same class means same word. -/// 0: blank -/// 1: punctuation groups -/// 2: normal word character -/// >2: multi-byte word character. -static int get_mouse_class(char_u *p) -{ - if (MB_BYTE2LEN(p[0]) > 1) { - return mb_get_class(p); - } - - const int c = *p; - if (c == ' ' || c == '\t') { - return 0; - } - if (vim_iswordc(c)) { - return 2; - } - - // There are a few special cases where we want certain combinations of - // characters to be considered as a single word. These are things like - // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each - // character is in its own class. - if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { - return 1; - } - return c; -} - /// End Visual mode. /// This function should ALWAYS be called to end Visual mode, except from /// do_pending_operator(). @@ -3066,7 +2209,7 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) /// @return true if line[offset] is not inside a C-style comment or string, /// false otherwise. -static bool is_ident(char_u *line, int offset) +static bool is_ident(const char_u *line, int offset) { bool incomment = false; int instring = 0; @@ -3417,7 +2560,7 @@ static void nv_mousescroll(cmdarg_T *cap) if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); - } else { + } else if (p_mousescroll_vert > 0) { cap->count1 = p_mousescroll_vert; cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); @@ -5697,6 +4840,10 @@ static void nv_gomark(cmdarg_T *cap) { int name; MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + if (cap->oap->op_type != OP_NOP) { + // When there is a pending operator, do not restore the view as this is usually unexpected. + flags = 0; + } MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -5748,7 +4895,7 @@ static void nv_pcmark(cmdarg_T *cap) fm = get_changelist(curbuf, curwin, (int)cap->count1); } else { fm = get_jumplist(curwin, (int)cap->count1); - flags |= KMarkNoContext | kMarkJumpList; + flags |= KMarkNoContext | kMarkJumpList; } // Changelist and jumplist have their own error messages. Therefore avoid // calling nv_mark_move_to() when not found to avoid incorrect error @@ -6518,8 +5665,7 @@ static void n_opencmd(cmdarg_T *cap) if (u_save((linenr_T)(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0)), (linenr_T)(curwin->w_cursor.lnum + - (cap->cmdchar == 'o' ? 1 : 0)) - ) + (cap->cmdchar == 'o' ? 1 : 0))) && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, 0, NULL)) { @@ -7398,12 +6544,6 @@ static void nv_event(cmdarg_T *cap) } } -/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". -static bool mouse_model_popup(void) -{ - return p_mousem[0] == 'p'; -} - void normal_cmd(oparg_T *oap, bool toplevel) { NormalState s; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 29008200a7..2d53918ded 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -95,8 +95,7 @@ struct block_def { // The names of operators. // IMPORTANT: Index must correspond with defines in vim.h!!! // The third field indicates whether the operator always works on lines. -static char opchars[][3] = -{ +static char opchars[][3] = { { NUL, NUL, 0 }, // OP_NOP { 'd', NUL, OPF_CHANGE }, // OP_DELETE { 'y', NUL, 0 }, // OP_YANK @@ -3273,7 +3272,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) shortline = (vcol < col) || (vcol == col && !*ptr); - if (vcol < col) { // line too short, padd with spaces + if (vcol < col) { // line too short, pad with spaces bd.startspaces = col - vcol; } else if (vcol > col) { bd.endspaces = vcol - col; diff --git a/src/nvim/option.c b/src/nvim/option.c index 0b819aad43..8142be4eb1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2648,6 +2648,10 @@ void check_redraw(uint32_t flags) status_redraw_all(); } + if ((flags & P_RTABL) || all) { // mark tablines dirty + redraw_tabline = true; + } + if ((flags & P_RBUF) || (flags & P_RWIN) || all) { changed_window_setting(); } @@ -3256,7 +3260,7 @@ static void showoptions(int all, int opt_flags) } /// Return true if option "p" has its default value. -static int optval_default(vimoption_T *p, char_u *varp) +static int optval_default(vimoption_T *p, const char_u *varp) { if (varp == NULL) { return true; // hidden option is always at default @@ -3974,6 +3978,8 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_cfu); case PV_OFU: return (char_u *)&(curbuf->b_p_ofu); + case PV_EOF: + return (char_u *)&(curbuf->b_p_eof); case PV_EOL: return (char_u *)&(curbuf->b_p_eol); case PV_FIXEOL: @@ -4889,7 +4895,7 @@ static void option_value2string(vimoption_T *opp, int opt_flags) /// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be /// printed as a keyname. /// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'. -static int wc_use_keyname(char_u *varp, long *wcp) +static int wc_use_keyname(const char_u *varp, long *wcp) { if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) { *wcp = *(long *)varp; @@ -5214,6 +5220,17 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of 'formatlistpat'. +/// +/// @param buf The buffer. +char *get_flp_value(buf_T *buf) +{ + if (buf->b_p_flp == NULL || *buf->b_p_flp == NUL) { + return p_flp; + } + return buf->b_p_flp; +} + /// Get the local or global value of the 'virtualedit' flags. unsigned int get_ve_flags(void) { diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index c4333a6f61..7c33773e41 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -24,6 +24,7 @@ #define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output // when option changed, what to display: +#define P_RTABL 0x800U ///< redraw tabline #define P_RSTAT 0x1000U ///< redraw status lines #define P_RWIN 0x2000U ///< redraw current window and recompute text #define P_RBUF 0x4000U ///< redraw current buffer and recompute text @@ -496,6 +497,7 @@ EXTERN char_u *p_ef; // 'errorfile' EXTERN char *p_efm; // 'errorformat' EXTERN char *p_gefm; // 'grepformat' EXTERN char *p_gp; // 'grepprg' +EXTERN int p_eof; ///< 'endoffile' EXTERN int p_eol; ///< 'endofline' EXTERN char *p_ei; // 'eventignore' EXTERN int p_et; ///< 'expandtab' @@ -857,6 +859,7 @@ enum { BV_CFU, BV_DEF, BV_INC, + BV_EOF, BV_EOL, BV_FIXEOL, BV_EP, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 8a883a09c3..3a59becb33 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -19,7 +19,7 @@ -- types: bool, number, string -- lists: (nil), comma, onecomma, flags, flagscomma -- scopes: global, buffer, window --- redraw options: statuslines, current_window, curent_window_only, +-- redraw options: statuslines, tabline, current_window, current_window_only, -- current_buffer, all_windows, curswant -- defaults: {condition=#if condition, if_true=default, if_false=default} -- #if condition: @@ -641,6 +641,15 @@ return { defaults={if_true=macros('ENC_DFLT')} }, { + full_name='endoffile', abbreviation='eof', + short_desc=N_("write CTRL-Z for last line in file"), + type='bool', scope={'buffer'}, + no_mkrc=true, + redraw={'statuslines'}, + varname='p_eof', + defaults={if_true=true} + }, + { full_name='endofline', abbreviation='eol', short_desc=N_("write <EOL> for last line in file"), type='bool', scope={'buffer'}, @@ -2407,7 +2416,7 @@ return { short_desc=N_("custom format for the console tab pages line"), type='string', scope={'global'}, modelineexpr=true, - redraw={'statuslines'}, + redraw={'tabline'}, varname='p_tal', defaults={if_true=""} }, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 43628d2842..65bc9f60df 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -697,6 +697,10 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf if (briopt_check(curwin) == FAIL) { errmsg = e_invarg; } + // list setting requires a redraw + if (curwin->w_briopt_list) { + redraw_all_later(UPD_NOT_VALID); + } } else if (varp == &p_isi || varp == &(curbuf->b_p_isk) || varp == &p_isp @@ -1601,6 +1605,12 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf setmouse(); // in case 'mouse' changed } + // Changing Formatlistpattern when briopt includes the list setting: + // redraw + if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) { + redraw_all_later(UPD_NOT_VALID); + } + if (curwin->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { curwin->w_set_curswant = true; diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index bd79b43574..faafc546a4 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -1119,10 +1119,16 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si len = envlen; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // if (!one) skip to separator: space or comma. while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) { *dst_p++ = *src++; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // Skip separator. while ((*src == ' ' || *src == ',') && --dstlen > 0) { *dst_p++ = *src++; diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 426dc422c2..da23a54c4e 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -37,7 +37,7 @@ typedef enum { ///< EAGAIN was encountered. } FileOpenFlags; -static inline bool file_eof(const FileDescriptor *const fp) +static inline bool file_eof(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Check whether end of file was encountered @@ -51,7 +51,7 @@ static inline bool file_eof(const FileDescriptor *const fp) return fp->eof && rbuffer_size(fp->rv) == 0; } -static inline int file_fd(const FileDescriptor *const fp) +static inline int file_fd(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Return the file descriptor associated with the FileDescriptor structure diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 396bf6986a..161c8d28b8 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -67,7 +67,7 @@ void os_delay(uint64_t ms, bool ignoreinput) } LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int); } else { - os_microdelay(ms * 1000u, ignoreinput); + os_microdelay(ms * 1000U, ignoreinput); } } @@ -80,10 +80,10 @@ void os_delay(uint64_t ms, bool ignoreinput) /// If false, waiting is aborted on any input. void os_microdelay(uint64_t us, bool ignoreinput) { - uint64_t elapsed = 0u; + uint64_t elapsed = 0U; uint64_t base = uv_hrtime(); // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. - const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX; + const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX; uv_mutex_lock(&delay_mutex); @@ -92,7 +92,7 @@ void os_microdelay(uint64_t us, bool ignoreinput) // Else we check for input in ~100ms intervals. const uint64_t ns_delta = ignoreinput ? ns - elapsed - : MIN(ns - elapsed, 100000000u); // 100ms + : MIN(ns - elapsed, 100000000U); // 100ms const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); if (0 != rv && UV_ETIMEDOUT != rv) { diff --git a/src/nvim/path.c b/src/nvim/path.c index 981937b79e..f568ba8854 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1244,7 +1244,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i add_pat = expand_backtick(&ga, (char *)p, flags); if (add_pat == -1) { recursive = false; - FreeWild(ga.ga_len, ga.ga_data); + ga_clear_strings(&ga); *num_file = 0; *file = NULL; return FAIL; @@ -2362,16 +2362,9 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force) } #endif if (p != NULL) { - // relative to root - if (p == fname) { - // only one path component - relative_directory[0] = PATHSEP; - relative_directory[1] = NUL; - } else { - assert(p >= fname); - memcpy(relative_directory, fname, (size_t)(p - fname)); - relative_directory[p - fname] = NUL; - } + assert(p >= fname); + memcpy(relative_directory, fname, (size_t)(p - fname + 1)); + relative_directory[p - fname + 1] = NUL; end_of_path = p + 1; } else { relative_directory[0] = NUL; diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 42218ac847..bed15f9e36 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -444,9 +444,9 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) // May have to add something for 'breakindent' and/or 'showbreak' // string at start of line. // Set *headp to the size of what we add. + // Do not use 'showbreak' at the NUL after the text. added = 0; - - char *const sbr = (char *)get_showbreak_value(wp); + char *const sbr = c == NUL ? empty_option : (char *)get_showbreak_value(wp); if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 57896b74ce..1db21880bb 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -48,10 +48,16 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) list(SORT NVIM_RELATIVE_SOURCES) add_custom_command( OUTPUT ${NVIM_POT} + COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + -S ${CMAKE_CURRENT_SOURCE_DIR}/tojavascript.vim ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim COMMAND ${XGETTEXT_PRG} -o ${NVIM_POT} --default-domain=nvim - --add-comments --keyword=_ --keyword=N_ -D ${CMAKE_CURRENT_SOURCE_DIR} - ${NVIM_RELATIVE_SOURCES} - DEPENDS ${NVIM_SOURCES}) + --add-comments --keyword=_ --keyword=N_ --keyword=NGETTEXT:1,2 + -D ${CMAKE_CURRENT_SOURCE_DIR} -D ${CMAKE_CURRENT_BINARY_DIR} + ${NVIM_RELATIVE_SOURCES} optwin.js + COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + -S ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim ${NVIM_POT} ../../../runtime/optwin.vim + VERBATIM + DEPENDS ${NVIM_SOURCES} nvim nvim_runtime_deps) set(LANGUAGE_MO_FILES) set(UPDATE_PO_TARGETS) diff --git a/src/nvim/po/fixfilenames.vim b/src/nvim/po/fixfilenames.vim new file mode 100644 index 0000000000..04bc0791c0 --- /dev/null +++ b/src/nvim/po/fixfilenames.vim @@ -0,0 +1,13 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. + +set shortmess+=A + +for name in argv()[1:] + let jsname = fnamemodify(name, ":t:r") .. ".js" + exe "%s+" .. jsname .. "+" .. substitute(name, '\\', '/', 'g') .. "+" +endfor + +write +last +quit diff --git a/src/nvim/po/tojavascript.vim b/src/nvim/po/tojavascript.vim new file mode 100644 index 0000000000..7868570be7 --- /dev/null +++ b/src/nvim/po/tojavascript.vim @@ -0,0 +1,18 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. +" Javascript is used because, like Vim, it accepts both single and double +" quoted strings. + +set shortmess+=A + +for name in argv()[1:] + exe 'edit ' .. fnameescape(name) + + " Strip comments + g/^\s*"/s/.*// + + " Write as .js file, xgettext recognizes them + exe 'w! ' .. fnamemodify(name, ":t:r") .. ".js" +endfor + +quit diff --git a/src/nvim/profile.c b/src/nvim/profile.c index 50a8a371f5..b588431bda 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -398,7 +398,7 @@ bool prof_def_func(void) /// Print the count and times for one function or function line. /// /// @param prefer_self when equal print only self time -static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self, +static void prof_func_line(FILE *fd, int count, const proftime_T *total, const proftime_T *self, bool prefer_self) { if (count > 0) { @@ -684,7 +684,7 @@ void script_prof_save(proftime_T *tm) } /// Count time spent in children after invoking another script or function. -void script_prof_restore(proftime_T *tm) +void script_prof_restore(const proftime_T *tm) { scriptitem_T *si; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index af1db1956b..5d101ee415 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -337,8 +337,7 @@ static const size_t LINE_MAXLEN = 4096; static struct fmtpattern { char convchar; char *pattern; -} fmt_pat[FMT_PATTERNS] = -{ +} fmt_pat[FMT_PATTERNS] = { { 'f', ".\\+" }, // only used when at end { 'n', "\\d\\+" }, // 1 { 'l', "\\d\\+" }, // 2 @@ -563,7 +562,7 @@ static size_t efm_regpat_bufsz(char *efm) } /// Return the length of a 'errorformat' option part (separated by ","). -static int efm_option_part_len(char *efm) +static int efm_option_part_len(const char *efm) { int len; @@ -5257,7 +5256,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp /// Jump to the first match and update the directory. static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy, - buf_T *first_match_buf, char *target_dir) + buf_T *first_match_buf, char *target_dir) // NOLINT(readability-non-const-parameter) { buf_T *buf = curbuf; qf_jump(qi, 0, 0, forceit); @@ -5326,10 +5325,8 @@ static int vgr_process_args(exarg_T *eap, vgr_args_T *args) } // Parse the list of arguments, wildcards have already been expanded. - if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) { - return FAIL; - } - if (args->fcount == 0) { + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL + || args->fcount == 0) { emsg(_(e_nomatch)); return FAIL; } @@ -7066,7 +7063,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char void ex_helpgrep(exarg_T *eap) { qf_info_T *qi = &ql_info; - char *au_name = NULL; + char *au_name = NULL; switch (eap->cmdidx) { case CMD_helpgrep: diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index e87382ff7c..7a96889f22 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -97,20 +97,24 @@ static int toggle_Magic(int x) #define MAX_LIMIT (32767L << 16L) -static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); -static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); -static char_u e_large_class[] = N_("E945: Range too large in character class"); -static char_u e_unmatchedpp[] = N_("E53: Unmatched %s%%("); -static char_u e_unmatchedp[] = N_("E54: Unmatched %s("); -static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); -static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); -static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); -static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); -static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); -static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); -static char_u e_regexp_number_after_dot_pos_search[] +static char e_missingbracket[] = N_("E769: Missing ] after %s["); +static char e_reverse_range[] = N_("E944: Reverse range in character class"); +static char e_large_class[] = N_("E945: Range too large in character class"); +static char e_unmatchedpp[] = N_("E53: Unmatched %s%%("); +static char e_unmatchedp[] = N_("E54: Unmatched %s("); +static char e_unmatchedpar[] = N_("E55: Unmatched %s)"); +static char e_z_not_allowed[] = N_("E66: \\z( not allowed here"); +static char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); +static char e_missing_sb[] = N_("E69: Missing ] after %s%%["); +static char e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char e_regexp_number_after_dot_pos_search_chr[] = N_("E1204: No Number allowed after .: '\\%%%c'"); -static char_u e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); +static char e_nfa_regexp_missing_value_in_chr[] + = N_("E1273: (NFA regexp) missing value in '\\%%%c'"); +static char e_atom_engine_must_be_at_start_of_pattern[] + = N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern"); +static char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); #define NOT_MULTI 0 #define MULTI_ONE 1 @@ -181,8 +185,7 @@ static int backslash_trans(int c) /// recognized. Otherwise "pp" is advanced to after the item. static int get_char_class(char **pp) { - static const char *(class_names[]) = - { + static const char *(class_names[]) = { "alnum:]", #define CLASS_ALNUM 0 "alpha:]", @@ -240,7 +243,7 @@ static int get_char_class(char **pp) * Specific version of character class functions. * Using a table to keep this fast. */ -static short class_tab[256]; +static int16_t class_tab[256]; #define RI_DIGIT 0x01 #define RI_HEX 0x02 @@ -482,13 +485,33 @@ static char_u *skip_anyof(char *p) } /// Skip past regular expression. -/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc). +/// Stop at end of "startp" or where "delim" is found ('/', '?', etc). /// Take care of characters with a backslash in front of it. /// Skip strings inside [ and ]. +char *skip_regexp(char *startp, int delim, int magic) +{ + return skip_regexp_ex(startp, delim, magic, NULL, NULL); +} + +/// Call skip_regexp() and when the delimiter does not match give an error and +/// return NULL. +char *skip_regexp_err(char *startp, int delim, int magic) +{ + char *p = skip_regexp(startp, delim, magic); + + if (*p != delim) { + semsg(_("E654: missing delimiter after search pattern: %s"), startp); + return NULL; + } + return p; +} + +/// skip_regexp() with extra arguments: /// When "newp" is not NULL and "dirc" is '?', make an allocated copy of the /// expression and change "\?" to "?". If "*newp" is not NULL the expression /// is changed in-place. -char *skip_regexp(char *startp, int dirc, int magic, char **newp) +/// If a "\?" is changed to "?" then "dropped" is incremented, unless NULL. +char *skip_regexp_ex(char *startp, int dirc, int magic, char **newp, int *dropped) { int mymagic; char *p = startp; @@ -517,6 +540,9 @@ char *skip_regexp(char *startp, int dirc, int magic, char **newp) *newp = xstrdup(startp); p = *newp + (p - startp); } + if (dropped != NULL) { + (*dropped)++; + } STRMOVE(p, p + 1); } else { p++; // skip next character @@ -1328,8 +1354,7 @@ typedef struct { } decomp_T; // 0xfb20 - 0xfb4f -static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = -{ +static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = { { 0x5e2, 0, 0 }, // 0xfb20 alt ayin { 0x5d0, 0, 0 }, // 0xfb21 alt alef { 0x5d3, 0, 0 }, // 0xfb22 alt dalet @@ -2276,16 +2301,14 @@ list_T *reg_submatch_list(int no) # include "nvim/regexp_nfa.c" #endif -static regengine_T bt_regengine = -{ +static regengine_T bt_regengine = { bt_regcomp, bt_regfree, bt_regexec_nl, bt_regexec_multi, }; -static regengine_T nfa_regengine = -{ +static regengine_T nfa_regengine = { nfa_regcomp, nfa_regfree, nfa_regexec_nl, diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index ac33fc0f13..6f63b38a90 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -1971,6 +1971,11 @@ static char_u *regatom(int *flagp) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } ret = regnode(CURSOR); break; @@ -2091,6 +2096,7 @@ static char_u *regatom(int *flagp) uint32_t n = 0; int cmp; bool cur = false; + bool got_digit = false; cmp = c; if (cmp == '<' || cmp == '>') { @@ -2101,6 +2107,7 @@ static char_u *regatom(int *flagp) c = getchr(); } while (ascii_isdigit(c)) { + got_digit = true; n = n * 10 + (uint32_t)(c - '0'); c = getchr(); } @@ -2115,9 +2122,9 @@ static char_u *regatom(int *flagp) *regcode++ = (char_u)cmp; } break; - } else if (c == 'l' || c == 'c' || c == 'v') { + } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) { if (cur && n) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); rc_did_emsg = true; return NULL; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index fbd4e26c75..d4d2ed28cc 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -2094,6 +2094,12 @@ static int nfa_regatom(void) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 + && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } EMIT(NFA_CURSOR); break; @@ -2141,6 +2147,7 @@ static int nfa_regatom(void) int64_t n = 0; const int cmp = c; bool cur = false; + bool got_digit = false; if (c == '<' || c == '>') { c = getchr(); @@ -2151,7 +2158,7 @@ static int nfa_regatom(void) } while (ascii_isdigit(c)) { if (cur) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); return FAIL; } if (n > (INT32_MAX - (c - '0')) / 10) { @@ -2161,10 +2168,15 @@ static int nfa_regatom(void) } n = n * 10 + (c - '0'); c = getchr(); + got_digit = true; } if (c == 'l' || c == 'c' || c == 'v') { int32_t limit = INT32_MAX; + if (!cur && !got_digit) { + semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c)); + return FAIL; + } if (c == 'l') { if (cur) { n = curwin->w_cursor.lnum; @@ -3358,8 +3370,8 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) int last = indent->ga_len - 3; char_u save[2]; - STRNCPY(save, &p[last], 2); - STRNCPY(&p[last], "+-", 2); + STRNCPY(save, &p[last], 2); // NOLINT(runtime/printf) + memcpy(&p[last], "+-", 2); fprintf(debugf, " %s", p); STRNCPY(&p[last], save, 2); // NOLINT(runtime/printf) } else { @@ -4623,6 +4635,20 @@ static bool sub_equal(regsub_T *sub1, regsub_T *sub2) } #ifdef REGEXP_DEBUG +static void open_debug_log(TriState result) +{ + log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); + if (log_fd == NULL) { + emsg(_(e_log_open_failed)); + log_fd = stderr; + } + + fprintf(log_fd, "****************************\n"); + fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); + fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE"); + fprintf(log_fd, "****************************\n"); +} + static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim) { int col; @@ -4635,6 +4661,9 @@ static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int li col = (int)(sub->list.line[0].start - rex.line); } nfa_set_code(state->c); + if (log_fd == NULL) { + open_debug_log(kNone); + } fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n", action, abs(state->id), lid, state->c, code, col, pim_info(pim)); @@ -5656,16 +5685,7 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T nfa_endp = save_nfa_endp; #ifdef REGEXP_DEBUG - log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "****************************\n"); - fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK"); - fprintf(log_fd, "****************************\n"); - } else { - emsg(_(e_log_open_failed)); - log_fd = stderr; - } + open_debug_log(result); #endif return result; @@ -5971,16 +5991,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef REGEXP_DEBUG log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "**********************************\n"); - nfa_set_code(start->c); - fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", - abs(start->id), code); - fprintf(log_fd, "**********************************\n"); - } else { + if (log_fd == NULL) { emsg(_(e_log_open_failed)); log_fd = stderr; } + fprintf(log_fd, "**********************************\n"); + nfa_set_code(start->c); + fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", + abs(start->id), code); + fprintf(log_fd, "**********************************\n"); #endif thislist = &list[0]; diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 935bf4c507..6becd50910 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -343,7 +343,7 @@ RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src) return dst; } -void runtime_search_path_unref(RuntimeSearchPath path, int *ref) +void runtime_search_path_unref(RuntimeSearchPath path, const int *ref) FUNC_ATTR_NONNULL_ALL { if (*ref) { @@ -1633,7 +1633,7 @@ void ex_options(exarg_T *eap) bool multi_mods = 0; buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); + (void)add_win_cmd_modifiers(buf, &cmdmod, &multi_mods); os_setenv("OPTWIN_CMD", buf, 1); cmd_source(SYS_OPTWIN_FILE, NULL); diff --git a/src/nvim/search.c b/src/nvim/search.c index e404d00b60..2f3e5a2cb6 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -80,8 +80,7 @@ // one for other searches. last_idx points to the one that was used the last // time. -static struct spat spats[2] = -{ +static struct spat spats[2] = { // Last used search pattern [0] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL }, // Last used substitute pattern @@ -1081,7 +1080,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // Find end of regular expression. // If there is a matching '/' or '?', toss it. ps = (char_u *)strcopy; - p = (char_u *)skip_regexp((char *)pat, search_delim, p_magic, &strcopy); + p = (char_u *)skip_regexp_ex((char *)pat, search_delim, p_magic, &strcopy, NULL); if (strcopy != (char *)ps) { // made a copy of "pat" to change "\?" to "?" searchcmdlen += (int)(STRLEN(pat) - strlen(strcopy)); diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c index 012f145875..6522158f12 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -339,7 +339,7 @@ bool sha256_self_test(void) } } - if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE)) { + if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE) != 0) { failures = true; output[sizeof(output) - 1] = '\0'; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index e56febec9b..40b3429de9 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -166,7 +166,7 @@ typedef enum { /// Possible results of shada_write function. typedef enum { - kSDWriteSuccessfull, ///< Writing was successful. + kSDWriteSuccessful, ///< Writing was successful. kSDWriteReadNotShada, ///< Writing was successful, but when reading it ///< attempted to read file that did not look like ///< a ShaDa file. @@ -1151,8 +1151,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { - hms_init(&hms[i], i, (size_t)p_hi, true, true); + for (HistoryType i = 0; i < HIST_COUNT; i++) { + hms_init(&hms[i], (uint8_t)i, (size_t)p_hi, true, true); } } ShadaEntry cur_entry; @@ -1416,12 +1416,12 @@ shada_read_main_cycle_end: // memory for the history string itself and separator character which // may be assigned right away. if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { hms_insert_whole_neovim_history(&hms[i]); clr_history(i); int *new_hisidx; int *new_hisnum; - histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum); + histentry_T *hist = hist_get_array((uint8_t)i, &new_hisidx, &new_hisnum); if (hist != NULL) { hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum); } @@ -1476,7 +1476,7 @@ static char *shada_filename(const char *file) file = p_shadafile; } else { if ((file = (char *)find_shada_parameter('n')) == NULL || *file == NUL) { - file = shada_get_default_file(); + file = shada_get_default_file(); } // XXX It used to be one level lower, so that whatever is in // `p_shadafile` was expanded. I intentionally moved it here @@ -1511,7 +1511,7 @@ static char *shada_filename(const char *file) /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. /// -/// @return kSDWriteSuccessfull, kSDWriteFailed or kSDWriteIgnError. +/// @return kSDWriteSuccessful, kSDWriteFailed or kSDWriteIgnError. static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntry entry, const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL @@ -1557,6 +1557,18 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr (sd_default_values[(entry).type].data.attr == (entry).data.attr) #define ONE_IF_NOT_DEFAULT(entry, attr) \ ((size_t)(!CHECK_DEFAULT(entry, attr))) + +#define PACK_BOOL(entry, name, attr) \ + do { \ + if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ + PACK_STATIC_STR(name); \ + if (sd_default_values[(entry).type].data.search_pattern.attr) { \ + msgpack_pack_false(spacker); \ + } else { \ + msgpack_pack_true(spacker); \ + } \ + } \ + } while (0) switch (entry.type) { case kSDItemMissing: abort(); @@ -1640,17 +1652,6 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(SEARCH_KEY_PAT); PACK_BIN(cstr_as_string(entry.data.search_pattern.pat)); -#define PACK_BOOL(entry, name, attr) \ - do { \ - if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ - PACK_STATIC_STR(name); \ - if (sd_default_values[(entry).type].data.search_pattern.attr) { \ - msgpack_pack_false(spacker); \ - } else { \ - msgpack_pack_true(spacker); \ - } \ - } \ - } while (0) PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic); PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used); PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase); @@ -1818,7 +1819,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr } msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); - return kSDWriteSuccessfull; + return kSDWriteSuccessful; shada_pack_entry_error: msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); @@ -1838,7 +1839,7 @@ static inline ShaDaWriteResult shada_pack_pfreed_entry(msgpack_packer *const pac const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ret = shada_pack_entry(packer, entry.data, max_kbyte); if (entry.can_free_entry) { shada_free_shada_entry(&entry.data); @@ -1957,6 +1958,28 @@ static const char *shada_format_entry(const ShadaEntry entry) ret[0] = 0; vim_snprintf(S_LEN(ret), "%s", "[ ] ts=%" PRIu64 " "); // ^ Space for `can_free_entry` +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add(S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + (void *)entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) switch (entry.type) { case kSDItemMissing: vim_snprintf_add(S_LEN(ret), "Missing"); @@ -1985,28 +2008,6 @@ static const char *shada_format_entry(const ShadaEntry entry) case kSDItemVariable: vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); break; -#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ - do { \ - typval_T ad_tv = { \ - .v_type = VAR_DICT, \ - .vval.v_dict = entry.data.filemark.additional_data \ - }; \ - size_t ad_len; \ - char *const ad = encode_tv2string(&ad_tv, &ad_len); \ - vim_snprintf_add(S_LEN(ret), \ - entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ - "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ - "ad={%p:[%zu]%.64s} }", \ - name_fmt_arg, \ - strlen(entry.data.filemark.fname), \ - entry.data.filemark.fname, \ - entry.data.filemark.mark.lnum, \ - entry.data.filemark.mark.col, \ - entry.data.filemark.mark.coladd, \ - (void *)entry.data.filemark.additional_data, \ - ad_len, \ - ad); \ - } while (0) case kSDItemGlobalMark: FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); break; @@ -2052,9 +2053,35 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re msgpack_packer *const packer) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ShadaEntry entry; ShaDaReadResult srni_ret; + +#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ + do { \ + PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ + if (wms_entry->data.type != kSDItemMissing) { \ + if (wms_entry->data.timestamp >= (entry).timestamp) { \ + shada_free_shada_entry(&(entry)); \ + break; \ + } \ + if (wms_entry->can_free_entry) { \ + shada_free_shada_entry(&wms_entry->data); \ + } \ + } \ + *wms_entry = pfs_entry; \ + } while (0) + +#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ + do { \ + if ((entry).can_free_entry) { \ + shada_free_shada_entry(&(entry).data); \ + } \ + } while (0) + +#define SDE_TO_PFSDE(entry) \ + ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) + while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags, max_kbyte)) != kSDReadStatusFinished) { @@ -2072,20 +2099,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re case kSDReadStatusMalformed: continue; } -#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ - do { \ - PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ - if (wms_entry->data.type != kSDItemMissing) { \ - if (wms_entry->data.timestamp >= (entry).timestamp) { \ - shada_free_shada_entry(&(entry)); \ - break; \ - } \ - if (wms_entry->can_free_entry) { \ - shada_free_shada_entry(&wms_entry->data); \ - } \ - } \ - *wms_entry = pfs_entry; \ - } while (0) const PossiblyFreedShadaEntry pfs_entry = { .can_free_entry = true, .data = entry, @@ -2225,14 +2238,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re *wms_entry = pfs_entry; } } else { -#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ - do { \ - if ((entry).can_free_entry) { \ - shada_free_shada_entry(&(entry).data); \ - } \ - } while (0) -#define SDE_TO_PFSDE(entry) \ - ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) #define AFTERFREE_DUMMY(entry) #define DUMMY_IDX_ADJ(i) MERGE_JUMPS(filemarks->changes_size, filemarks->changes, @@ -2487,7 +2492,7 @@ static int hist_type2char(const int type) static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; @@ -2511,7 +2516,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef bool dump_history = false; // Initialize history merger - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { long num_saved = get_shada_parameter(hist_type2char(i)); if (num_saved == -1) { num_saved = p_hi; @@ -2519,7 +2524,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (num_saved > 0) { dump_history = true; dump_one_history[i] = true; - hms_init(&wms->hms[i], i, (size_t)num_saved, sd_reader != NULL, false); + hms_init(&wms->hms[i], (uint8_t)i, (size_t)num_saved, sd_reader != NULL, false); } else { dump_one_history[i] = false; } @@ -2647,7 +2652,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef } tv_clear(&vartv); tv_clear(&tgttv); - if (spe_ret == kSDWriteSuccessfull) { + if (spe_ret == kSDWriteSuccessful) { int kh_ret; (void)kh_put(strset, &wms->dumped_variables, name, &kh_ret); } @@ -2812,7 +2817,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (sd_reader != NULL) { const ShaDaWriteResult srww_ret = shada_read_when_writing(sd_reader, srni_flags, max_kbyte, wms, packer); - if (srww_ret != kSDWriteSuccessfull) { + if (srww_ret != kSDWriteSuccessful) { ret = srww_ret; } } @@ -3073,7 +3078,7 @@ shada_write_file_nomerge: {} if (!nomerge) { sd_reader.close(&sd_reader); bool did_remove = false; - if (sw_ret == kSDWriteSuccessfull) { + if (sw_ret == kSDWriteSuccessful) { #ifdef UNIX // For Unix we check the owner of the file. It's not very nice to // overwrite a user’s viminfo file after a "su root", with a diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 9c517098b9..8c0ae6dca8 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -1271,7 +1271,7 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, c } /// ":sign unplace" command -static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group) +static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); @@ -1328,7 +1328,7 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, /// :sign jump {id} buffer={nr} /// :sign jump {id} group={group} file={fname} /// :sign jump {id} group={group} buffer={nr} -static void sign_jump_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group) +static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (sign_name == NULL && group == NULL && id == -1) { emsg(_(e_argreq)); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index adfbbb9573..e76ac49b5d 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -50,7 +50,7 @@ // Use SPELL_PRINTTREE for debugging: dump the word tree after adding a word. // Only use it for small word lists! -// Use SPELL_COMPRESS_ALLWAYS for debugging: compress the word tree after +// Use SPELL_COMPRESS_ALWAYS for debugging: compress the word tree after // adding a word. Only use it for small word lists! // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a @@ -160,7 +160,7 @@ typedef struct matchinf_S { win_T *mi_win; // buffer being checked // for NOBREAK - int mi_result2; // "mi_resul" without following word + int mi_result2; // "mi_result" without following word char_u *mi_end2; // "mi_end" without following word } matchinf_T; @@ -960,7 +960,7 @@ bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) // compound rule. This is used to stop trying a compound if the flags // collected so far can't possibly match any compound rule. // Caller must check that slang->sl_comprules is not NULL. -bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, const char_u *compflags) { // loop over all the COMPOUNDRULE entries for (char_u *p = slang->sl_comprules; *p != NUL; p++) { @@ -2184,7 +2184,7 @@ static void use_midword(slang_T *lp, win_T *wp) // Find the region "region[2]" in "rp" (points to "sl_regions"). // Each region is simply stored as the two characters of its name. // Returns the index if found (first is 0), REGION_ALL if not found. -static int find_region(char_u *rp, char_u *region) +static int find_region(const char_u *rp, const char_u *region) { int i; @@ -2209,7 +2209,7 @@ static int find_region(char_u *rp, char_u *region) /// @param[in] end End of word or NULL for NUL delimited string /// /// @returns Case type of word -int captype(char_u *word, char_u *end) +int captype(char_u *word, const char_u *end) FUNC_ATTR_NONNULL_ARG(1) { char_u *p; @@ -2814,7 +2814,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". // Multi-byte version of spell_soundfold(). -static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) +static void spell_soundfold_wsal(slang_T *slang, const char_u *inword, char_u *res) { salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; int word[MAXWLEN] = { 0 }; diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index d7a3efda83..7837f242b5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -415,7 +415,7 @@ struct wordnode_S { // "wn_region" the LSW of the wordnr. char_u wn_affixID; // supported/required prefix ID or 0 uint16_t wn_flags; // WF_ flags - short wn_region; // region mask + int16_t wn_region; // region mask #ifdef SPELL_PRINTTREE int wn_nr; // sequence nr for printing @@ -819,7 +819,7 @@ endOK: // Fill in the wordcount fields for a trie. // Returns the total number of words. -static void tree_count_words(char_u *byts, idx_T *idxs) +static void tree_count_words(const char_u *byts, idx_T *idxs) { int depth; idx_T arridx[MAXWLEN]; @@ -2171,18 +2171,14 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) midword = (char_u *)getroom_save(spin, (char_u *)items[1]); } else if (is_aff_rule(items, itemcnt, "TRY", 2)) { // ignored, we look in the tree for what chars may appear - } - // TODO: remove "RAR" later - else if ((is_aff_rule(items, itemcnt, "RAR", 2) - || is_aff_rule(items, itemcnt, "RARE", 2)) - && aff->af_rare == 0) { + } else if ((is_aff_rule(items, itemcnt, "RAR", 2) // TODO(vim): remove "RAR" later + || is_aff_rule(items, itemcnt, "RARE", 2)) + && aff->af_rare == 0) { aff->af_rare = affitem2flag(aff->af_flagtype, (char_u *)items[1], fname, lnum); - } - // TODO: remove "KEP" later - else if ((is_aff_rule(items, itemcnt, "KEP", 2) - || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) - && aff->af_keepcase == 0) { + } else if ((is_aff_rule(items, itemcnt, "KEP", 2) // TODO(vim): remove "KEP" later + || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) + && aff->af_keepcase == 0) { aff->af_keepcase = affitem2flag(aff->af_flagtype, (char_u *)items[1], fname, lnum); } else if ((is_aff_rule(items, itemcnt, "BAD", 2) @@ -3962,8 +3958,8 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co // When "flags" < 0 we are adding to the prefix tree where "flags" is used for // "rare" and "region" is the condition nr. // Returns FAIL when out of memory. -static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int flags, int region, - int affixID) +static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root, int flags, + int region, int affixID) { wordnode_T *node = root; wordnode_T *np; @@ -4093,7 +4089,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // 3. When compressed before, added "compress_added" words // (si_compress_cnt == 1) and the number of free nodes drops below the // maximum word length. -#ifndef SPELL_COMPRESS_ALLWAYS +#ifndef SPELL_COMPRESS_ALWAYS if (spin->si_compress_cnt == 1 // NOLINT(readability/braces) ? spin->si_free_count < MAXWLEN : spin->si_blocks_cnt >= compress_start) @@ -5742,7 +5738,7 @@ static void init_spellfile(void) /// Set the spell character tables from strings in the .spl file. /// /// @param cnt length of "flags" -static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) +static void set_spell_charflags(const char_u *flags, int cnt, char_u *fol) { // We build the new tables here first, so that we can compare with the // previous one. diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 2ff41d5157..400579a233 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -2250,7 +2250,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; if (*ftp->ft_from != *p) { // past possible matching entries - sp->ts_curi = (char_u)gap->ga_len; + sp->ts_curi = (int16_t)gap->ga_len; break; } if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 @@ -3530,7 +3530,7 @@ static int soundalike_score(char *goodstart, char *badstart) /// The implementation of the algorithm comes from Aspell editdist.cpp, /// edit_distance(). It has been converted from C++ to C and modified to /// support multi-byte characters. -static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) +static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u *goodword) { int *cnt; int j, i; @@ -3635,7 +3635,8 @@ static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodw /// Multi-byte version of spell_edit_score_limit(). /// Keep it in sync with the above! -static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit) +static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const char_u *goodword, + int limit) { limitscore_T stack[10]; // allow for over 3 * 2 edits int stackidx; diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index c4329fd84d..fb49a1b6a7 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -829,7 +829,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // so `vim_strsize` will work. char *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; - long group_len = vim_strsize(t); + ptrdiff_t group_len = vim_strsize(t); // If the group contained internal items // and the group did not have a minimum width, @@ -915,7 +915,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // If the group is shorter than the minimum width, add padding characters. } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; + ptrdiff_t min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -929,7 +929,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san group_len = (min_group_width - group_len) * utf_char2len(fillchar); memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { - group_len = (long)(out_end_p - out_p); + group_len = out_end_p - out_p; } out_p += group_len; // } diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 10173fac1d..4d1401293b 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1041,7 +1041,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t break; } if (arg > 0) { - arg_sign = 1; + arg_sign = 1; } else if (arg < 0) { arg_sign = -1; } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 527cca05f5..fb82df4fe9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3344,8 +3344,7 @@ static int last_matchgroup; static void syn_list_one(const int id, const bool syncing, const bool link_only) { bool did_header = false; - static struct name_list namelist1[] = - { + static struct name_list namelist1[] = { { HL_DISPLAY, "display" }, { HL_CONTAINED, "contained" }, { HL_ONELINE, "oneline" }, @@ -3358,8 +3357,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) { HL_CONCEALENDS, "concealends" }, { 0, NULL } }; - static struct name_list namelist2[] = - { + static struct name_list namelist2[] = { { HL_SKIPWHITE, "skipwhite" }, { HL_SKIPNL, "skipnl" }, { HL_SKIPEMPTY, "skipempty" }, @@ -4751,7 +4749,7 @@ static char *get_syn_pattern(char *arg, synpat_T *ci) return NULL; } - end = skip_regexp(arg + 1, *arg, true, NULL); + end = skip_regexp(arg + 1, *arg, true); if (*end != *arg) { // end delimiter not found semsg(_("E401: Pattern delimiter not found: %s"), arg); return NULL; @@ -4904,7 +4902,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) finished = true; break; } - arg_end = skip_regexp(next_arg + 1, *next_arg, true, NULL); + arg_end = skip_regexp(next_arg + 1, *next_arg, true); if (*arg_end != *next_arg) { // end delimiter not found illegal = true; break; @@ -5238,8 +5236,7 @@ struct subcommand { void (*func)(exarg_T *, int); // function to call }; -static struct subcommand subcommands[] = -{ +static struct subcommand subcommands[] = { { "case", syn_cmd_case }, { "clear", syn_cmd_clear }, { "cluster", syn_cmd_cluster }, @@ -5304,7 +5301,7 @@ void ex_ownsyntax(exarg_T *eap) curwin->w_s = xcalloc(1, sizeof(synblock_T)); hash_init(&curwin->w_s->b_keywtab); hash_init(&curwin->w_s->b_keywtab_ic); - // TODO: Keep the spell checking as it was. NOLINT(readability/todo) + // TODO(vim): Keep the spell checking as it was. curwin->w_p_spell = false; // No spell checking // make sure option values are "empty_option" instead of NULL clear_string_option(&curwin->w_s->b_p_spc); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 7c465ac909..264f961b43 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -252,8 +252,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) new_tag = true; } else { - if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : tagstacklen == 0) { // empty stack emsg(_(e_tagstack)); @@ -643,8 +642,6 @@ end_do_tag: } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time - - return; } // List all the matching tags. @@ -1495,7 +1492,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc // Try tag file names from tags option one by one. for (first_file = true; - get_tagfname(&tn, first_file, (char *)tag_fname) == OK; + get_tagfname(&tn, first_file, tag_fname) == OK; first_file = false) { // A file that doesn't exist is silently ignored. Only when not a // single file is found, an error message is given (further on). @@ -1553,7 +1550,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc } } - if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) { + if ((fp = os_fopen(tag_fname, "r")) == NULL) { continue; } @@ -1647,7 +1644,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc // skip empty and blank lines do { eof = vim_fgets((char_u *)lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline((char *)lbuf)); + } while (!eof && vim_isblankline(lbuf)); if (eof) { break; // end of file @@ -1953,7 +1950,7 @@ parse_line: // Decide in which array to store this match. is_current = test_for_current((char *)tagp.fname, (char *)tagp.fname_end, - (char *)tag_fname, + tag_fname, buf_ffname); is_static = test_for_static(&tagp); @@ -2673,7 +2670,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // anything following. str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false, NULL) + 1; + str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; @@ -2886,7 +2883,7 @@ static int find_extra(char_u **pp) if (ascii_isdigit(*str)) { str = (char_u *)skipdigits((char *)str + 1); } else if (*str == '/' || *str == '?') { - str = (char_u *)skip_regexp((char *)str + 1, *str, false, NULL); + str = (char_u *)skip_regexp((char *)str + 1, *str, false); if (*str != first_char) { str = NULL; } else { @@ -3292,10 +3289,12 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); } + if (action == 't') { // truncate the stack taggy_T *const tagstack = wp->w_tagstack; const int tagstackidx = wp->w_tagstackidx; int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 50574b292d..5e221e13df 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -428,7 +428,7 @@ bool terminal_enter(void) long save_w_p_so = curwin->w_p_so; long save_w_p_siso = curwin->w_p_siso; if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { - if (strcmp(curwin->w_p_culopt, "number")) { + if (strcmp(curwin->w_p_culopt, "number") != 0) { save_w_p_culopt = curwin->w_p_culopt; curwin->w_p_culopt = xstrdup("number"); } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 4641408069..a6d1cf1003 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -179,4 +179,4 @@ newtestssilent: $(NEW_TESTS_RES) @echo "[OLDTEST] Running" $* @rm -rf $*.failed test.ok $(RM_ON_RUN) @mkdir -p $(TMPDIR) - @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE -S runtest.vim $*.vim + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE --cmd "set shortmess-=F" -S runtest.vim $*.vim diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 4107df99d6..1459b70243 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -4,6 +4,9 @@ source term_util.vim " Command to check for the presence of a feature. command -nargs=1 CheckFeature call CheckFeature(<f-args>) func CheckFeature(name) + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if !has(a:name) throw 'Skipped: ' .. a:name .. ' feature missing' endif @@ -39,6 +42,22 @@ func CheckFunction(name) endif endfunc +" Command to check for the presence of an Ex command +command -nargs=1 CheckCommand call CheckCommand(<f-args>) +func CheckCommand(name) + if !exists(':' .. a:name) + throw 'Skipped: ' .. a:name .. ' command not supported' + endif +endfunc + +" Command to check for the presence of a shell command +command -nargs=1 CheckExecutable call CheckExecutable(<f-args>) +func CheckExecutable(name) + if !executable(a:name) + throw 'Skipped: ' .. a:name .. ' program not executable' + endif +endfunc + " Command to check for the presence of python. Argument should have been " obtained with PythonProg() func CheckPython(name) diff --git a/test/benchmark/samples/re.freeze.txt b/src/nvim/testdir/samples/re.freeze.txt index d768c23c5e..d768c23c5e 100644 --- a/test/benchmark/samples/re.freeze.txt +++ b/src/nvim/testdir/samples/re.freeze.txt diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index c2809844ac..ef7cc4ac5f 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -364,4 +364,19 @@ func GetMessages() return msg_list endfunc +" Run the list of commands in 'cmds' and look for 'errstr' in exception. +" Note that assert_fails() cannot be used in some places and this function +" can be used. +func AssertException(cmds, errstr) + let save_exception = '' + try + for cmd in a:cmds + exe cmd + endfor + catch + let save_exception = v:exception + endtry + call assert_match(a:errstr, save_exception) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test49.ok b/src/nvim/testdir/test49.ok index 9f283e808b..d687f5bc77 100644 --- a/src/nvim/testdir/test49.ok +++ b/src/nvim/testdir/test49.ok @@ -1,48 +1,4 @@ Results of test49.vim: -*** Test 18: OK (67224583) -*** Test 19: OK (69275973) -*** Test 20: OK (1874575085) -*** Test 21: OK (147932225) -*** Test 22: OK (4161) -*** Test 23: OK (49) -*** Test 24: OK (41) -*** Test 27: OK (1996459) -*** Test 28: OK (1996459) -*** Test 29: OK (170428555) -*** Test 30: OK (190905173) -*** Test 31: OK (190905173) -*** Test 34: OK (2146584868) -*** Test 35: OK (2146584868) -*** Test 36: OK (1071644672) -*** Test 37: OK (1071644672) -*** Test 38: OK (357908480) -*** Test 39: OK (357908480) -*** Test 40: OK (357908480) -*** Test 49: OK (179000669) -*** Test 50: OK (363550045) -*** Test 52: OK (1247112011) -*** Test 53: OK (131071) -*** Test 54: OK (2047) -*** Test 55: OK (1023) -*** Test 56: OK (511) -*** Test 57: OK (2147450880) -*** Test 58: OK (624945) -*** Test 59: OK (2038431743) -*** Test 60: OK (311511339) -*** Test 61: OK (374889517) -*** Test 62: OK (286331153) -*** Test 63: OK (236978127) -*** Test 64: OK (1499645335) -*** Test 65: OK (70187) -*** Test 66: OK (5464) -*** Test 67: OK (212514423) -*** Test 68: OK (212514423) -*** Test 76: OK (1610087935) -*** Test 77: OK (1388671) -*** Test 78: OK (134217728) -*** Test 79: OK (70288929) -*** Test 80: OK (17895765) -*** Test 81: OK (387) *** Test 82: OK (8454401) *** Test 83: OK (2835) *** Test 84: OK (934782101) diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 3ee5e9ff7c..d5f8cd3b60 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2019 Nov 03 +" Last Change: 2020 Jun 07 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -456,7 +456,7 @@ function ExtraVim(...) " messing up the user's viminfo file. let redirect = a:0 ? \ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : "" - exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect . + exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -Xes" . redirect . \ " -c 'debuggreedy|set viminfo+=nviminfo'" . \ " -c 'let ExtraVimBegin = " . extra_begin . "'" . \ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints . @@ -607,1660 +607,6 @@ com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) " END_OF_TEST_ENVIRONMENT - do not change or remove this line. - -" Tests 1 to 17 were moved to test_vimscript.vim -let Xtest = 18 - -"------------------------------------------------------------------------------- -" Test 18: Interrupt (Ctrl-C pressed) {{{1 -" -" On an interrupt, the script processing is terminated immediately. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - if 1 - Xpath 1 " X: 1 - while 1 - Xpath 2 " X: 2 - if 1 - Xpath 4 " X: 4 - "INTERRUPT - Xpath 8 " X: 0 - break - finish - endif | Xpath 16 " X: 0 - Xpath 32 " X: 0 - endwhile | Xpath 64 " X: 0 - Xpath 128 " X: 0 - endif | Xpath 256 " X: 0 - Xpath 512 " X: 0 -endif - -if ExtraVim() - try - Xpath 1024 " X: 1024 - "INTERRUPT - Xpath 2048 " X: 0 - endtry | Xpath 4096 " X: 0 - Xpath 8192 " X: 0 -endif - -if ExtraVim() - function! F() - if 1 - Xpath 16384 " X: 16384 - while 1 - Xpath 32768 " X: 32768 - if 1 - Xpath 65536 " X: 65536 - "INTERRUPT - Xpath 131072 " X: 0 - break - return - endif | Xpath 262144 " X: 0 - Xpath Xpath 524288 " X: 0 - endwhile | Xpath 1048576 " X: 0 - Xpath Xpath 2097152 " X: 0 - endif | Xpath Xpath 4194304 " X: 0 - Xpath Xpath 8388608 " X: 0 - endfunction - - call F() | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - function! G() - try - Xpath 67108864 " X: 67108864 - "INTERRUPT - Xpath 134217728 " X: 0 - endtry | Xpath 268435456 " X: 0 - Xpath 536870912 " X: 0 - endfunction - - call G() | Xpath 1073741824 " X: 0 - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 -endif - -Xcheck 67224583 - - -"------------------------------------------------------------------------------- -" Test 19: Aborting on errors inside :try/:endtry {{{1 -" -" An error in a command dynamically enclosed in a :try/:endtry region -" aborts script processing immediately. It does not matter whether -" the failing command is outside or inside a function and whether a -" function has an "abort" attribute. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() abort - Xpath 1 " X: 1 - asdf - Xpath 2 " X: 0 - endfunction - - try - Xpath 4 " X: 4 - call F() - Xpath 8 " X: 0 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 -endif - -if ExtraVim() - function! G() - Xpath 64 " X: 64 - asdf - Xpath 128 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - call G() - Xpath 512 " X: 0 - endtry | Xpath 1024 " X: 0 - Xpath 2048 " X: 0 -endif - -if ExtraVim() - try - Xpath 4096 " X: 4096 - asdf - Xpath 8192 " X: 0 - endtry | Xpath 16384 " X: 0 - Xpath 32768 " X: 0 -endif - -if ExtraVim() - if 1 - try - Xpath 65536 " X: 65536 - asdf - Xpath 131072 " X: 0 - endtry | Xpath 262144 " X: 0 - endif | Xpath 524288 " X: 0 - Xpath 1048576 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - asdf - Xpath 4194304 " X: 0 - endtry | Xpath 8388608 " X: 0 - endwhile | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 -" try - Xpath 67108864 " X: 67108864 - endwhile | Xpath 134217728 " X: 0 - Xpath 268435456 " X: 0 -endif - -Xcheck 69275973 -"------------------------------------------------------------------------------- -" Test 20: Aborting on errors after :try/:endtry {{{1 -" -" When an error occurs after the last active :try/:endtry region has -" been left, termination behavior is as if no :try/:endtry has been -" seen. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 1 " X: 1 - endtry - asdf - endwhile | Xpath 2 " X: 0 - Xpath 4 " X: 4 -endif - -if ExtraVim() - while 1 - try - Xpath 8 " X: 8 - break - Xpath 16 " X: 0 - endtry - endwhile - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 64 -endif - -if ExtraVim() - while 1 - try - Xpath 128 " X: 128 - break - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - endwhile - Xpath 1024 " X: 1024 - asdf - Xpath 2048 " X: 2048 -endif - -if ExtraVim() - while 1 - try - Xpath 4096 " X: 4096 - finally - Xpath 8192 " X: 8192 - break - Xpath 16384 " X: 0 - endtry - endwhile - Xpath 32768 " X: 32768 - asdf - Xpath 65536 " X: 65536 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 131072 " X: 131072 - continue - Xpath 262144 " X: 0 - endtry - endwhile - Xpath 524288 " X: 524288 - asdf - Xpath 1048576 " X: 1048576 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - continue - Xpath 4194304 " X: 0 - finally - Xpath 8388608 " X: 8388608 - endtry - endwhile - Xpath 16777216 " X: 16777216 - asdf - Xpath 33554432 " X: 33554432 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - continue - Xpath 268435456 " X: 0 - endtry - endwhile - Xpath 536870912 " X: 536870912 - asdf - Xpath 1073741824 " X: 1073741824 -endif - -Xcheck 1874575085 - - -"------------------------------------------------------------------------------- -" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 -" -" If a :try conditional stays inactive due to a preceding :continue, -" :break, :return, or :finish, its :finally clause should not be -" executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function F() - let loops = 2 - XloopINIT! 1 256 - while loops > 0 - XloopNEXT - let loops = loops - 1 - try - if loops == 1 - Xloop 1 " X: 1 - continue - Xloop 2 " X: 0 - elseif loops == 0 - Xloop 4 " X: 4*256 - break - Xloop 8 " X: 0 - endif - - try " inactive - Xloop 16 " X: 0 - finally - Xloop 32 " X: 0 - endtry - finally - Xloop 64 " X: 64 + 64*256 - endtry - Xloop 128 " X: 0 - endwhile - - try - Xpath 65536 " X: 65536 - return - Xpath 131072 " X: 0 - try " inactive - Xpath 262144 " X: 0 - finally - Xpath 524288 " X: 0 - endtry - finally - Xpath 1048576 " X: 1048576 - endtry - Xpath 2097152 " X: 0 - endfunction - - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 8388608 - finish - Xpath 16777216 " X: 0 - try " inactive - Xpath 33554432 " X: 0 - finally - Xpath 67108864 " X: 0 - endtry - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 -endif - -Xcheck 147932225 - - -"------------------------------------------------------------------------------- -" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 -" -" If a :try conditional stays inactive due to a preceding error or -" interrupt or :throw, its :finally clause should not be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! Error() - try - asdf " aborting error, triggering error exception - endtry - endfunction - - Xpath 1 " X: 1 - call Error() - Xpath 2 " X: 0 - - if 1 " not active due to error - try " not active since :if inactive - Xpath 4 " X: 0 - finally - Xpath 8 " X: 0 - endtry - endif - - try " not active due to error - Xpath 16 " X: 0 - finally - Xpath 32 " X: 0 - endtry -endif - -if ExtraVim() - function! Interrupt() - try - "INTERRUPT " triggering interrupt exception - endtry - endfunction - - Xpath 64 " X: 64 - call Interrupt() - Xpath 128 " X: 0 - - if 1 " not active due to interrupt - try " not active since :if inactive - Xpath 256 " X: 0 - finally - Xpath 512 " X: 0 - endtry - endif - - try " not active due to interrupt - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 0 - endtry -endif - -if ExtraVim() - function! Throw() - throw "xyz" - endfunction - - Xpath 4096 " X: 4096 - call Throw() - Xpath 8192 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 0 - endtry - endif - - try " not active due to :throw - Xpath 65536 " X: 0 - finally - Xpath 131072 " X: 0 - endtry -endif - -Xcheck 4161 - - -"------------------------------------------------------------------------------- -" Test 23: :catch clauses for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" none of its :catch clauses should be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - throw "xyz" - Xpath 2 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 4 " X: 0 - catch /xyz/ - Xpath 8 " X: 0 - endtry - endif - catch /xyz/ - Xpath 16 " X: 16 - endtry - - Xpath 32 " X: 32 - throw "abc" - Xpath 64 " X: 0 - - try " not active due to :throw - Xpath 128 " X: 0 - catch /abc/ - Xpath 256 " X: 0 - endtry -endif - -Xcheck 49 - - -"------------------------------------------------------------------------------- -" Test 24: :endtry for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" its :endtry should not rethrow the exception to the next surrounding -" active :try conditional. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try " try 1 - try " try 2 - Xpath 1 " X: 1 - throw "xyz" " makes try 2 inactive - Xpath 2 " X: 0 - - try " try 3 - Xpath 4 " X: 0 - endtry " no rethrow to try 1 - catch /xyz/ " should catch although try 2 inactive - Xpath 8 " X: 8 - endtry - catch /xyz/ " try 1 active, but exception already caught - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 32 -endif - -Xcheck 41 - -" Tests 25 and 26 were moved to test_trycatch.vim -let Xtest = 27 - - -"------------------------------------------------------------------------------- -" Test 27: Executing :finally clauses after :return {{{1 -" -" For a :return command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the called function is ended. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - return - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry - Xpath 16 " X: 0 - finally - Xpath 32 " X: 32 - endtry - Xpath 64 " X: 0 -endfunction - -function! G() - try - Xpath 128 " X: 128 - return - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - call F() - Xpath 1024 " X: 1024 - endtry - Xpath 2048 " X: 0 -endfunction - -function! H() - try - Xpath 4096 " X: 4096 - call G() - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - return - Xpath 32768 " X: 0 - endtry - Xpath 65536 " X: 0 -endfunction - -try - Xpath 131072 " X: 131072 - call H() - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -Xcheck 1996459 - -" Leave F, G, and H for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 28: Executing :finally clauses after :finish {{{1 -" -" For a :finish command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the sourced file is finished. -" -" This test executes the bodies of the functions F, G, and H from the -" previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptF = MakeScript("F") " X: 1 + 2 + 8 + 32 -let scriptG = MakeScript("G", scriptF) " X: 128 + 512 + 1024 -let scriptH = MakeScript("H", scriptG) " X: 4096 + 8192 + 16384 - -try - Xpath 131072 " X: 131072 - exec "source" scriptH - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -call delete(scriptF) -call delete(scriptG) -call delete(scriptH) -unlet scriptF scriptG scriptH -delfunction F -delfunction G -delfunction H - -Xcheck 1996459 - - -"------------------------------------------------------------------------------- -" Test 29: Executing :finally clauses on errors {{{1 -" -" After an error in a command dynamically enclosed in a :try/:endtry -" region, :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() - while 1 - try - Xpath 1 " X: 1 - while 1 - try - Xpath 2 " X: 2 - asdf " error - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 - break - endwhile - Xpath 64 " X: 0 - finally - Xpath 128 " X: 128 - endtry | Xpath 256 " X: 0 - Xpath 512 " X: 0 - break - endwhile - Xpath 1024 " X: 0 - endfunction - - while 1 - try - Xpath 2048 " X: 2048 - while 1 - call F() - Xpath 4096 " X: 0 - break - endwhile | Xpath 8192 " X: 0 - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 32768 - endtry | Xpath 65536 " X: 0 - endwhile | Xpath 131072 " X: 0 - Xpath 262144 " X: 0 -endif - -if ExtraVim() - function! G() abort - if 1 - try - Xpath 524288 " X: 524288 - asdf " error - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - endtry | Xpath 4194304 " X: 0 - endif | Xpath 8388608 " X: 0 - Xpath 16777216 " X: 0 - endfunction - - if 1 - try - Xpath 33554432 " X: 33554432 - call G() - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry | Xpath 268435456 " X: 0 - endif | Xpath 536870912 " X: 0 - Xpath 1073741824 " X: 0 -endif - -Xcheck 170428555 - - -"------------------------------------------------------------------------------- -" Test 30: Executing :finally clauses on interrupt {{{1 -" -" After an interrupt in a command dynamically enclosed in -" a :try/:endtry region, :finally clauses are executed and the -" script processing is terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - "INTERRUPT - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - "INTERRUPT - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - "INTERRUPT - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - - -"------------------------------------------------------------------------------- -" Test 31: Executing :finally clauses after :throw {{{1 -" -" After a :throw dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - throw "exception" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - throw "exception" - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - throw "exception" - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - -" Tests 32 and 33 were moved to test_trycatch.vim -let Xtest = 34 - - -"------------------------------------------------------------------------------- -" Test 34: :finally reason discarded by :continue {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :continue in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! C(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - continue " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - Xloop 2 " X: 0 - elseif loop == 2 - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endif - endwhile - endfunction - - call C("continue") - Xpath 2097152 " X: 2097152 - call C("break") - Xpath 4194304 " X: 4194304 - call C("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript C - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call C("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call C("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call C("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction C - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 35: :finally reason discarded by :break {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :break in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! B(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - break " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endfunction - - call B("continue") - Xpath 2097152 " X: 2097152 - call B("break") - Xpath 4194304 " X: 4194304 - call B("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript B - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call B("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call B("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call B("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction B - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 36: :finally reason discarded by :return {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :return in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! R(jump, retval) abort - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - return a:retval " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let sum = -R("continue", -8) - Xpath 2097152 " X: 2097152 - let sum = sum - R("break", -16) - Xpath 4194304 " X: 4194304 - let sum = sum - R("return", -32) - Xpath 8388608 " X: 8388608 - try - let sum = sum - R("error", -64) - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let sum = sum - R("interrupt", -128) - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - let sum = sum - R("throw", -256) - Xpath 268435456 " X: 268435456 - endtry - endtry - Xpath 536870912 " X: 536870912 - - let expected = 8 + 16 + 32 + 64 + 128 + 256 - if sum != expected - Xpath 1073741824 " X: 0 - Xout "sum =" . sum . ", expected: " . expected - endif - - unlet sum expected - delfunction R - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 37: :finally reason discarded by :finish {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :finish in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! F(jump) " not executed as function, transformed to a script - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "finish" - finish - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - finish " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let scriptF = MakeScript("F") - delfunction F - - let g:jump = "continue" - exec "source" scriptF - Xpath 2097152 " X: 2097152 - let g:jump = "break" - exec "source" scriptF - Xpath 4194304 " X: 4194304 - let g:jump = "finish" - exec "source" scriptF - Xpath 8388608 " X: 8388608 - try - let g:jump = "error" - exec "source" scriptF - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let g:jump = "interrupt" - exec "source" scriptF - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - try - let g:jump = "throw" - exec "source" scriptF - Xpath 268435456 " X: 268435456 - finally - Xpath 536870912 " X: 536870912 - endtry - endtry - endtry - unlet g:jump - - call delete(scriptF) - unlet scriptF - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 38: :finally reason discarded by an error {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an error in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! E(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - asdf " error; discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call E("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call E("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call E("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript E - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call E("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call E("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call E("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction E - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 39: :finally reason discarded by an interrupt {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an interrupt in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! I(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - "INTERRUPT - discards jump that caused the :finally - let dummy = 0 - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call I("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call I("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call I("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript I - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call I("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call I("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call I("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction I - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 40: :finally reason discarded by :throw {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :throw in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! T(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - throw "xyz" " discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call T("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call T("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call T("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript T - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call T("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call T("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call T("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction T - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - -" Tests 41 to 48 were moved to test_trycatch.vim -let Xtest = 49 - - -"------------------------------------------------------------------------------- -" Test 49: Throwing exceptions across functions {{{1 -" -" When an exception is thrown but not caught inside a function, the -" caller is checked for a matching :catch clause. -"------------------------------------------------------------------------------- - -XpathINIT - -function! C() - try - Xpath 1 " X: 1 - throw "arrgh" - Xpath 2 " X: 0 - catch /arrgh/ - Xpath 4 " X: 4 - endtry - Xpath 8 " X: 8 -endfunction - -XloopINIT! 16 16 - -function! T1() - XloopNEXT - try - Xloop 1 " X: 16 + 16*16 - throw "arrgh" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 64 + 64*16 - endtry - Xloop 8 " X: 0 -endfunction - -function! T2() - try - Xpath 4096 " X: 4096 - call T1() - Xpath 8192 " X: 0 - finally - Xpath 16384 " X: 16384 - endtry - Xpath 32768 " X: 0 -endfunction - -try - Xpath 65536 " X: 65536 - call C() " throw and catch - Xpath 131072 " X: 131072 -catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 524288 " X: 524288 - call T1() " throw, one level - Xpath 1048576 " X: 0 -catch /arrgh/ - Xpath 2097152 " X: 2097152 -catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 8388608 " X: 8388608 - call T2() " throw, two levels - Xpath 16777216 " X: 0 -catch /arrgh/ - Xpath 33554432 " X: 33554432 -catch /.*/ - Xpath 67108864 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 134217728 " X: 134217728 - -Xcheck 179000669 - -" Leave C, T1, and T2 for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 50: Throwing exceptions across script files {{{1 -" -" When an exception is thrown but not caught inside a script file, -" the sourcing script or function is checked for a matching :catch -" clause. -" -" This test executes the bodies of the functions C, T1, and T2 from -" the previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptC = MakeScript("C") " X: 1 + 4 + 8 -delfunction C - -XloopINIT! 16 16 - -let scriptT1 = MakeScript("T1") " X: 16 + 64 + 16*16 + 64*16 -delfunction T1 - -let scriptT2 = MakeScript("T2", scriptT1) " X: 4096 + 16384 -delfunction T2 - -function! F() - try - Xpath 65536 " X: 65536 - exec "source" g:scriptC - Xpath 131072 " X: 131072 - catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - - try - Xpath 524288 " X: 524288 - exec "source" g:scriptT1 - Xpath 1048576 " X: 0 - catch /arrgh/ - Xpath 2097152 " X: 2097152 - catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -try - Xpath 8388608 " X: 8388608 - call F() - Xpath 16777216 " X: 16777216 - exec "source" scriptT2 - Xpath 33554432 " X: 0 -catch /arrgh/ - Xpath 67108864 " X: 67108864 -catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 268435456 " X: 268435456 - -call delete(scriptC) -call delete(scriptT1) -call delete(scriptT2) -unlet scriptC scriptT1 scriptT2 -delfunction F - -Xcheck 363550045 - -" Test 51 was moved to test_trycatch.vim -let Xtest = 52 - - -"------------------------------------------------------------------------------- -" Test 52: Uncaught exceptions {{{1 -" -" When an exception is thrown but not caught, an error message is -" displayed when the script is terminated. In case of an interrupt -" or error exception, the normal interrupt or error message(s) are -" displayed. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - function! MESSAGES(...) try exec "edit" g:msgfile @@ -2302,3627 +648,11 @@ function! MESSAGES(...) return match endfunction -if ExtraVim(msgfile) - Xpath 1 " X: 1 - throw "arrgh" -endif - -Xpath 2 " X: 2 -if !MESSAGES('E605', "Exception not caught") - Xpath 4 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8 " X: 8 - throw "oops" - catch /arrgh/ - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 0 -endif - -Xpath 64 " X: 64 -if !MESSAGES('E605', "Exception not caught") - Xpath 128 " X: 0 -endif - -if ExtraVim(msgfile) - function! T() - throw "brrr" - endfunction - - try - Xpath 256 " X: 256 - throw "arrgh" - catch /.*/ - Xpath 512 " X: 512 - call T() - endtry - Xpath 1024 " X: 0 -endif - -Xpath 2048 " X: 2048 -if !MESSAGES('E605', "Exception not caught") - Xpath 4096 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8192 " X: 8192 - throw "arrgh" - finally - Xpath 16384 " X: 16384 - throw "brrr" - endtry - Xpath 32768 " X: 0 -endif - -Xpath 65536 " X: 65536 -if !MESSAGES('E605', "Exception not caught") - Xpath 131072 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 262144 " X: 262144 - "INTERRUPT - endtry - Xpath 524288 " X: 0 -endif - -Xpath 1048576 " X: 1048576 -if !MESSAGES('INT', "Interrupted") - Xpath 2097152 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121/E15; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable", 'E15', "Invalid expression") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 134217728 " X: 134217728 -" unlet novar # " error E108/E488; exception: E488 - catch /E108:/ " should not catch - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 -endif - -Xpath 1073741824 " X: 1073741824 -if !MESSAGES('E108', "No such variable", 'E488', "Trailing characters") - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1247112011 - -" Leave MESSAGES() for the next tests. - - -"------------------------------------------------------------------------------- -" Test 53: Nesting errors: :endif/:else/:elseif {{{1 -" -" For nesting errors of :if conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endif -endif -if MESSAGES('E580', ":endif without :if") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" while 1 -" endif -" endwhile -endif -if MESSAGES('E580', ":endif without :if") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" else -endif -if MESSAGES('E581', ":else without :if") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" else -" endwhile -endif -if MESSAGES('E581', ":else without :if") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" elseif -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 1024 " X: 1024 -endif - -if ExtraVim(msgfile) -" while 1 -" elseif -" endwhile -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 2048 " X: 2048 -endif - -if ExtraVim(msgfile) -" try -" finally -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 4096 " X: 4096 -endif - -if ExtraVim(msgfile) -" try -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 8192 " X: 8192 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 16384 " X: 16384 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" else -" endif -endif -if MESSAGES('E583', "multiple :else") - Xpath 32768 " X: 32768 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" elseif 1 -" endif -endif -if MESSAGES('E584', ":elseif after :else") - Xpath 65536 " X: 65536 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 131071 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 54: Nesting errors: :while/:endwhile {{{1 -" -" For nesting errors of :while conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endwhile -" endif -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" finally -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1024 " X: 1024 -endif - - -call delete(msgfile) -unlet msgfile - -Xcheck 2047 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 55: Nesting errors: :continue/:break {{{1 -" -" For nesting errors of :continue and :break commands the correct -" error messages should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" continue -endif -if MESSAGES('E586', ":continue without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" continue -" endif -endif -if MESSAGES('E586', ":continue without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" break -endif -if MESSAGES('E587', ":break without :while") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" if 1 -" break -" endif -endif -if MESSAGES('E587', ":break without :while") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 512 " X: 512 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1023 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 56: Nesting errors: :endtry {{{1 -" -" For nesting errors of :try conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endtry -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endtry -" endif -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" endtry -" endwhile -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" try -" finally -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" try -" finally -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 256 " X: 256 -endif - -call delete(msgfile) -unlet msgfile - -delfunction MESSAGES - -Xcheck 511 - - -"------------------------------------------------------------------------------- -" Test 57: v:exception and v:throwpoint for user exceptions {{{1 -" -" v:exception evaluates to the value of the exception that was caught -" most recently and is not finished. (A caught exception is finished -" when the next ":catch", ":finally", or ":endtry" is reached.) -" v:throwpoint evaluates to the script/function name and line number -" where that exception has been thrown. -"------------------------------------------------------------------------------- - -XpathINIT - -function! FuncException() - let g:exception = v:exception -endfunction - -function! FuncThrowpoint() - let g:throwpoint = v:throwpoint -endfunction - -let scriptException = MakeScript("FuncException") -let scriptThrowPoint = MakeScript("FuncThrowpoint") - -command! CmdException let g:exception = v:exception -command! CmdThrowpoint let g:throwpoint = v:throwpoint - -XloopINIT! 1 2 - -function! CHECK(n, exception, throwname, throwline) - XloopNEXT - let error = 0 - if v:exception != a:exception - Xout a:n.": v:exception is" v:exception "instead of" a:exception - let error = 1 - endif - if v:throwpoint !~ a:throwname - let name = escape(a:throwname, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" name - let error = 1 - endif - if v:throwpoint !~ a:throwline - let line = escape(a:throwline, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if error - Xloop 1 " X: 0 - endif -endfunction - -function! T(arg, line) - if a:line == 2 - throw a:arg " in line 2 - elseif a:line == 4 - throw a:arg " in line 4 - elseif a:line == 6 - throw a:arg " in line 6 - elseif a:line == 8 - throw a:arg " in line 8 - endif -endfunction - -function! G(arg, line) - call T(a:arg, a:line) -endfunction - -function! F(arg, line) - call G(a:arg, a:line) -endfunction - -let scriptT = MakeScript("T") -let scriptG = MakeScript("G", scriptT) -let scriptF = MakeScript("F", scriptG) - -try - Xpath 32768 " X: 32768 - call F("oops", 2) -catch /.*/ - Xpath 65536 " X: 65536 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(1, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "let exception = v:exception" - exec "let throwpoint = v:throwpoint" - call CHECK(2, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - CmdException - CmdThrowpoint - call CHECK(3, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - call FuncException() - call FuncThrowpoint() - call CHECK(4, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "source" scriptException - exec "source" scriptThrowPoint - call CHECK(5, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - try - Xpath 131072 " X: 131072 - call G("arrgh", 4) - catch /.*/ - Xpath 262144 " X: 262144 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(6, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 524288 " X: 524288 - let g:arg = "autsch" - let g:line = 6 - exec "source" scriptF - catch /.*/ - Xpath 1048576 " X: 1048576 - let exception = v:exception - let throwpoint = v:throwpoint - " Symbolic links in tempname()s are not resolved, whereas resolving - " is done for v:throwpoint. Resolve the temporary file name for - " scriptT, so that it can be matched against v:throwpoint. - call CHECK(7, "autsch", resolve(scriptT), '\<6\>') - finally - Xpath 2097152 " X: 2097152 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(8, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 4194304 " X: 4194304 - let g:arg = "brrrr" - let g:line = 8 - exec "source" scriptG - catch /.*/ - Xpath 8388608 " X: 8388608 - let exception = v:exception - let throwpoint = v:throwpoint - " Resolve scriptT for matching it against v:throwpoint. - call CHECK(9, "brrrr", resolve(scriptT), '\<8\>') - finally - Xpath 16777216 " X: 16777216 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(10, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 33554432 " X: 33554432 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(11, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 67108864 " X: 67108864 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(12, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - finally - Xpath 134217728 " X: 134217728 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(13, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - endtry - Xpath 268435456 " X: 268435456 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(14, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') -finally - Xpath 536870912 " X: 536870912 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(15, "", '^$', '^$') -endtry - -Xpath 1073741824 " X: 1073741824 - -unlet exception throwpoint -delfunction FuncException -delfunction FuncThrowpoint -call delete(scriptException) -call delete(scriptThrowPoint) -unlet scriptException scriptThrowPoint -delcommand CmdException -delcommand CmdThrowpoint -delfunction T -delfunction G -delfunction F -call delete(scriptT) -call delete(scriptG) -call delete(scriptF) -unlet scriptT scriptG scriptF - -Xcheck 2147450880 - - -"------------------------------------------------------------------------------- -" -" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 -" -" v:exception and v:throwpoint work also for error and interrupt -" exceptions. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! T(line) - if a:line == 2 - delfunction T " error (function in use) in line 2 - elseif a:line == 4 - let dummy = 0 " INTERRUPT1 - interrupt in line 4 - endif - endfunction - - while 1 - try - Xpath 1 " X: 1 - let caught = 0 - call T(2) - catch /.*/ - let caught = 1 - if v:exception !~ 'Vim(delfunction):' - Xpath 2 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 4 " X: 0 - endif - if v:throwpoint !~ '\<2\>' - Xpath 8 " X: 0 - endif - finally - Xpath 16 " X: 16 - if caught || $VIMNOERRTHROW - Xpath 32 " X: 32 - endif - if v:exception != "" - Xpath 64 " X: 0 - endif - if v:throwpoint != "" - Xpath 128 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 256 " X: 256 - if v:exception != "" - Xpath 512 " X: 0 - endif - if v:throwpoint != "" - Xpath 1024 " X: 0 - endif - - while 1 - try - Xpath 2048 " X: 2048 - let caught = 0 - call T(4) - catch /.*/ - let caught = 1 - if v:exception != 'Vim:Interrupt' - Xpath 4096 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 8192 " X: 0 - endif - if v:throwpoint !~ '\<4\>' - Xpath 16384 " X: 0 - endif - finally - Xpath 32768 " X: 32768 - if caught || $VIMNOINTTHROW - Xpath 65536 " X: 65536 - endif - if v:exception != "" - Xpath 131072 " X: 0 - endif - if v:throwpoint != "" - Xpath 262144 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 524288 " X: 524288 - if v:exception != "" - Xpath 1048576 " X: 0 - endif - if v:throwpoint != "" - Xpath 2097152 " X: 0 - endif - -endif - -Xcheck 624945 - - -"------------------------------------------------------------------------------- -" -" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 -" -" When a :catch clause is left by a ":break" etc or an error or -" interrupt exception, v:exception and v:throwpoint are reset. They -" are not affected by an exception that is discarded before being -" caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 2 - - let sfile = expand("<sfile>") - - function! LineNumber() - return substitute(substitute(v:throwpoint, g:sfile, '', ""), - \ '\D*\(\d*\).*', '\1', "") - endfunction - - command! -nargs=1 SetLineNumber - \ try | throw "line" | catch /.*/ | let <args> = LineNumber() | endtry - - " Check v:exception/v:throwpoint against second/fourth parameter if - " specified, check for being empty else. - function! CHECK(n, ...) - XloopNEXT - let exception = a:0 != 0 ? a:1 : "" " second parameter (optional) - let emsg = a:0 != 0 ? a:2 : "" " third parameter (optional) - let line = a:0 != 0 ? a:3 : 0 " fourth parameter (optional) - let error = 0 - if emsg != "" - " exception is the error number, emsg the English error message text - if exception !~ '^E\d\+$' - Xout "TODO: Add message number for:" emsg - elseif v:lang == "C" || v:lang =~ '^[Ee]n' - if exception == "E492" && emsg == "Not an editor command" - let exception = '^Vim:' . exception . ': ' . emsg - else - let exception = '^Vim(\a\+):' . exception . ': ' . emsg - endif - else - if exception == "E492" - let exception = '^Vim:' . exception - else - let exception = '^Vim(\a\+):' . exception - endif - endif - endif - if exception == "" && v:exception != "" - Xout a:n.": v:exception is set:" v:exception - let error = 1 - elseif exception != "" && v:exception !~ exception - Xout a:n.": v:exception (".v:exception.") does not match" exception - let error = 1 - endif - if line == 0 && v:throwpoint != "" - Xout a:n.": v:throwpoint is set:" v:throwpoint - let error = 1 - elseif line != 0 && v:throwpoint !~ '\<' . line . '\>' - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if !error - Xloop 1 " X: 2097151 - endif - endfunction - - while 1 - try - throw "x1" - catch /.*/ - break - endtry - endwhile - call CHECK(1) - - while 1 - try - throw "x2" - catch /.*/ - break - finally - call CHECK(2) - endtry - break - endwhile - call CHECK(3) - - while 1 - try - let errcaught = 0 - try - try - throw "x3" - catch /.*/ - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(4, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(4) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(5) - - Xpath 2097152 " X: 2097152 - - while 1 - try - let intcaught = 0 - try - try - throw "x4" - catch /.*/ - SetLineNumber two_lines_before_interrupt - "INTERRUPT - let dummy = 0 - endtry - catch /.*/ - let intcaught = 1 - call CHECK(6, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(6) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(7) - - Xpath 4194304 " X: 4194304 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x5" - " missing endif - catch /.*/ - Xpath 8388608 " X: 0 - endtry - catch /.*/ - let errcaught = 1 - call CHECK(8, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(8) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(9) - - Xpath 16777216 " X: 16777216 - - try - while 1 - try - throw "x6" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 33554432 " X: 0 - endtry - call CHECK(10) - - try - while 1 - try - throw "x7" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 67108864 " X: 0 - finally - call CHECK(11) - endtry - call CHECK(12) - - while 1 - try - let errcaught = 0 - try - try - throw "x8" - finally - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(13, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(13) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(14) - - Xpath 134217728 " X: 134217728 - - while 1 - try - let intcaught = 0 - try - try - throw "x9" - finally - SetLineNumber two_lines_before_interrupt - "INTERRUPT - endtry - catch /.*/ - let intcaught = 1 - call CHECK(15, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(15) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(16) - - Xpath 268435456 " X: 268435456 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x10" - " missing endif - finally - call CHECK(17) - endtry - catch /.*/ - let errcaught = 1 - call CHECK(18, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(18) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(19) - - Xpath 536870912 " X: 536870912 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x11" - " missing endif - endtry - catch /.*/ - let errcaught = 1 - call CHECK(20, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(20) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(21) - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 2038431743 - - -"------------------------------------------------------------------------------- -" -" Test 60: (Re)throwing v:exception; :echoerr. {{{1 -" -" A user exception can be rethrown after catching by throwing -" v:exception. An error or interrupt exception cannot be rethrown -" because Vim exceptions cannot be faked. A Vim exception using the -" value of v:exception can, however, be triggered by the :echoerr -" command. -"------------------------------------------------------------------------------- - -XpathINIT - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /oops/ - Xpath 2 " X: 2 - throw v:exception " rethrow user exception - catch /.*/ - Xpath 4 " X: 0 - endtry -catch /^oops$/ " catches rethrown user exception - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 -endtry - -function! F() - try - let caught = 0 - try - Xpath 32 " X: 32 - write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e - Xpath 64 " X: 0 - Xout "did_emsg was reset before executing " . - \ "BufWritePost autocommands." - catch /^Vim(write):/ - let caught = 1 - throw v:exception " throw error: cannot fake Vim exception - catch /.*/ - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - endtry - catch /^Vim(throw):/ " catches throw error - let caught = caught + 1 - catch /.*/ - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 4096 " X: 0 - elseif caught - Xpath 8192 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call F() -delfunction F - -function! G() - try - let caught = 0 - try - Xpath 16384 " X: 16384 - asdf - catch /^Vim/ " catch error exception - let caught = 1 - " Trigger Vim error exception with value specified after :echoerr - let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - echoerr value - catch /.*/ - Xpath 32768 " X: 0 - finally - Xpath 65536 " X: 65536 - if !caught - if !$VIMNOERRTHROW - Xpath 131072 " X: 0 - else - let value = "Error" - echoerr value - endif - endif - endtry - catch /^Vim(echoerr):/ - let caught = caught + 1 - if v:exception !~ value - Xpath 262144 " X: 0 - endif - catch /.*/ - Xpath 524288 " X: 0 - finally - Xpath 1048576 " X: 1048576 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - elseif caught - Xpath 4194304 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call G() -delfunction G - -unlet! value caught - -if ExtraVim() - try - let errcaught = 0 - try - Xpath 8388608 " X: 8388608 - let intcaught = 0 - "INTERRUPT - catch /^Vim:/ " catch interrupt exception - let intcaught = 1 - " Trigger Vim error exception with value specified after :echoerr - echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - catch /.*/ - Xpath 16777216 " X: 0 - finally - Xpath 33554432 " X: 33554432 - if !intcaught - if !$VIMNOINTTHROW - Xpath 67108864 " X: 0 - else - echoerr "Interrupt" - endif - endif - endtry - catch /^Vim(echoerr):/ - let errcaught = 1 - if v:exception !~ "Interrupt" - Xpath 134217728 " X: 0 - endif - finally - Xpath 268435456 " X: 268435456 - if !errcaught && !$VIMNOERRTHROW - Xpath 536870912 " X: 0 - endif - endtry -endif - -Xcheck 311511339 - - -"------------------------------------------------------------------------------- -" Test 61: Catching interrupt exceptions {{{1 -" -" When an interrupt occurs inside a :try/:endtry region, an -" interrupt exception is thrown and can be caught. Its value is -" "Vim:Interrupt". If the interrupt occurs after an error or a :throw -" but before a matching :catch is reached, all following :catches of -" that try block are ignored, but the interrupt exception can be -" caught by the next surrounding try conditional. An interrupt is -" ignored when there is a previous interrupt that has not been caught -" or causes a :finally clause to be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - "INTERRUPT - Xpath 2 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 4 " X: 4 - if caught || $VIMNOINTTHROW - Xpath 8 " X: 8 - endif - endtry - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 0 - catch /do_not_catch/ - Xpath 128 " X: 0 - catch /.*/ "INTERRUPT - throw interrupt if !$VIMNOERRTHROW - Xpath 256 " X: 0 - catch /.*/ - Xpath 512 " X: 0 - finally "INTERRUPT - throw interrupt if $VIMNOERRTHROW - Xpath 1024 " X: 1024 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 2048 " X: 2048 - if caught || $VIMNOINTTHROW - Xpath 4096 " X: 4096 - endif - endtry - catch /.*/ - Xpath 8192 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 16384 " X: 16384 - throw "x" - Xpath 32768 " X: 0 - catch /do_not_catch/ - Xpath 65536 " X: 0 - catch /x/ "INTERRUPT - Xpath 131072 " X: 0 - catch /.*/ - Xpath 262144 " X: 0 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 524288 " X: 524288 - if caught || $VIMNOINTTHROW - Xpath 1048576 " X: 1048576 - endif - endtry - catch /.*/ - Xpath 2097152 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - let caught = 0 - try - Xpath 4194304 " X: 4194304 - "INTERRUPT - Xpath 8388608 " X: 0 - catch /do_not_catch/ "INTERRUPT - Xpath 16777216 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 33554432 " X: 33554432 - if caught || $VIMNOINTTHROW - Xpath 67108864 " X: 67108864 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - Xpath 268435456 " X: 268435456 - -endif - -Xcheck 374889517 - - -"------------------------------------------------------------------------------- -" Test 62: Catching error exceptions {{{1 -" -" An error inside a :try/:endtry region is converted to an exception -" and can be caught. The error exception has a "Vim(cmdname):" prefix -" where cmdname is the name of the failing command, or a "Vim:" prefix -" if no command name is known. The "Vim" prefixes cannot be faked. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -while 1 - try - try - let caught = 0 - unlet novar - catch /^Vim(unlet):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") - finally - Xpath 1 " X: 1 - if !caught && !$VIMNOERRTHROW - Xpath 2 " X: 0 - endif - if !MSG('E108', "No such variable") - Xpath 4 " X: 0 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw novar " error in :throw - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 16 " X: 16 - if !caught && !$VIMNOERRTHROW - Xpath 32 " X: 0 - endif - if caught ? !MSG('E121', "Undefined variable") - \ : !MSG('E15', "Invalid expression") - Xpath 64 " X: 0 - endif - endtry - catch /.*/ - Xpath 128 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw "Vim:faked" " error: cannot fake Vim exception - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E608', "Cannot :throw exceptions with 'Vim' prefix") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! F() - while 1 - " Missing :endwhile -endfunction - -while 1 - try - try - let caught = 0 - call F() - catch /^Vim(endfunction):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - ExecAsScript F - catch /^Vim:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! G() - call G() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call G() - catch /^Vim(call):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! H() - return H() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call H() - catch /^Vim(return):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") - finally - Xpath 16777216 " X: 16777216 - if !caught && !$VIMNOERRTHROW - Xpath 33554432 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 67108864 " X: 0 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet! caught mfd_save -delfunction F -delfunction G -delfunction H -Xpath 268435456 " X: 268435456 - -Xcheck 286331153 - -" Leave MSG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 63: Suppressing error exceptions by :silent!. {{{1 -" -" A :silent! command inside a :try/:endtry region suppresses the -" conversion of errors to an exception and the immediate abortion on -" error. When the commands executed by the :silent! themselves open -" a new :try/:endtry region, conversion of errors to exception and -" immediate abortion is switched on again - until the next :silent! -" etc. The :silent! has the effect of setting v:errmsg to the error -" message text (without displaying it) and continuing with the next -" script line. -" -" When a command triggering autocommands is executed by :silent! -" inside a :try/:endtry, the autocommand execution is not suppressed -" on error. -" -" This test reuses the function MSG() from the previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT! 1 4 - -let taken = "" - -function! S(n) abort - XloopNEXT - let g:taken = g:taken . "E" . a:n - let v:errmsg = "" - exec "asdf" . a:n - - " Check that ":silent!" continues: - Xloop 1 - - " Check that ":silent!" sets "v:errmsg": - if MSG('E492', "Not an editor command") - Xloop 2 - endif -endfunction - -function! Foo() - while 1 - try - try - let caught = 0 - " This is not silent: - call S(3) " X: 0 * 16 - catch /^Vim:/ - let caught = 1 - let errmsg3 = substitute(v:exception, '^Vim:', '', "") - silent! call S(4) " X: 3 * 64 - finally - if !caught - let errmsg3 = v:errmsg - " Do call S(4) here if not executed in :catch. - silent! call S(4) - endif - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - let v:errmsg = errmsg3 - if !MSG('E492', "Not an editor command") - Xpath 4194304 " X: 0 - endif - silent! call S(5) " X: 3 * 256 - " Break out of try conditionals that cover ":silent!". This also - " discards the aborting error when $VIMNOERRTHROW is non-zero. - break - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - endwhile - " This is a double ":silent!" (see caller). - silent! call S(6) " X: 3 * 1024 -endfunction - -function! Bar() - try - silent! call S(2) " X: 3 * 4 - " X: 3 * 4096 - silent! execute "call Foo() | call S(7)" - silent! call S(8) " X: 3 * 16384 - endtry " normal end of try cond that covers ":silent!" - " This has a ":silent!" from the caller: - call S(9) " X: 3 * 65536 -endfunction - -silent! call S(1) " X: 3 * 1 -silent! call Bar() -silent! call S(10) " X: 3 * 262144 - -let expected = "E1E2E3E4E5E6E7E8E9E10" -if taken != expected - Xpath 16777216 " X: 0 - Xout "'taken' is" taken "instead of" expected -endif - -augroup TMP - autocmd BufWritePost * Xpath 33554432 " X: 33554432 -augroup END - -Xpath 67108864 " X: 67108864 -write /i/m/p/o/s/s/i/b/l/e -Xpath 134217728 " X: 134217728 - -autocmd! TMP -unlet! caught errmsg3 taken expected -delfunction S -delfunction Foo -delfunction Bar -delfunction MSG - -Xcheck 236978127 - - -"------------------------------------------------------------------------------- -" Test 64: Error exceptions after error, interrupt or :throw {{{1 -" -" When an error occurs after an interrupt or a :throw but before -" a matching :catch is reached, all following :catches of that try -" block are ignored, but the error exception can be caught by the next -" surrounding try conditional. Any previous error exception is -" discarded. An error is ignored when there is a previous error that -" has not been caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - while 1 -" if 1 - " Missing :endif - endwhile " throw error exception - catch /^Vim(/ - let caught = 1 - finally - Xpath 2 " X: 2 - if caught || $VIMNOERRTHROW - Xpath 4 " X: 4 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - Xpath 16 " X: 16 - let caught = 0 - try -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 32 " X: 0 - catch /.*/ - Xpath 64 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 128 " X: 128 - if caught || $VIMNOERRTHROW - Xpath 256 " X: 256 - endif - endtry - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 1024 " X: 1024 - "INTERRUPT - catch /do_not_catch/ - Xpath 2048 " X: 0 -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 4096 " X: 0 - catch /.*/ - Xpath 8192 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 16384 " X: 16384 - if caught || $VIMNOERRTHROW - Xpath 32768 " X: 32768 - endif - endtry - catch /.*/ - Xpath 65536 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 131072 " X: 131072 - throw "x" - catch /do_not_catch/ - Xpath 262144 " X: 0 -" if 1 - " Missing :endif - catch /x/ " throw error exception - Xpath 524288 " X: 0 - catch /.*/ - Xpath 1048576 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 2097152 " X: 2097152 - if caught || $VIMNOERRTHROW - Xpath 4194304 " X: 4194304 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - Xpath 16777216 " X: 16777216 -" endif " :endif without :if; throw error exception -" if 1 - " Missing :endif - catch /do_not_catch/ " ignore new error - Xpath 33554432 " X: 0 - catch /^Vim(endif):/ - let caught = 1 - catch /^Vim(/ - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - if caught || $VIMNOERRTHROW - Xpath 268435456 " X: 268435456 - endif - endtry - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 1499645335 - - -"------------------------------------------------------------------------------- -" Test 65: Errors in the /pattern/ argument of a :catch {{{1 -" -" On an error in the /pattern/ argument of a :catch, the :catch does -" not match. Any following :catches of the same :try/:endtry don't -" match either. Finally clauses are executed. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /^oops$/ - Xpath 2 " X: 2 - catch /\)/ " not checked; exception has already been caught - Xpath 4 " X: 0 - endtry - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -function! F() - try - let caught = 0 - try - try - Xpath 32 " X: 32 - throw "ab" - catch /abc/ " does not catch - Xpath 64 " X: 0 - catch /\)/ " error; discards exception - Xpath 128 " X: 0 - catch /.*/ " not checked - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - Xpath 1024 " X: 0 - catch /^ab$/ " checked, but original exception is discarded - Xpath 2048 " X: 0 - catch /^Vim(catch):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(catch):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E475', "Invalid argument") - Xpath 16384 " X: 0 - endif - if !caught - return | " discard error - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -call F() -Xpath 65536 " X: 65536 - -delfunction MSG -delfunction F -unlet! caught - -Xcheck 70187 - - -"------------------------------------------------------------------------------- -" Test 66: Stop range :call on error, interrupt, or :throw {{{1 -" -" When a function which is multiply called for a range since it -" doesn't handle the range itself has an error in a command -" dynamically enclosed by :try/:endtry or gets an interrupt or -" executes a :throw, no more calls for the remaining lines in the -" range are made. On an error in a command not dynamically enclosed -" by :try/:endtry, the function is executed again for the remaining -" lines in the range. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let file = tempname() - exec "edit" file - - insert -line 1 -line 2 -line 3 -. - - XloopINIT! 1 2 - - let taken = "" - let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" - - function! F(reason, n) abort - let g:taken = g:taken . "F" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") . - \ "(" . line(".") . ")" - - if a:reason == "error" - asdf - elseif a:reason == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:reason == "throw" - throw "xyz" - elseif a:reason == "aborting error" - XloopNEXT - if g:taken != g:expected - Xloop 1 " X: 0 - Xout "'taken' is" g:taken "instead of" g:expected - endif - try - bwipeout! - call delete(file) - asdf - endtry - endif - endfunction - - function! G(reason, n) - let g:taken = g:taken . "G" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") - 1,3call F(a:reason, a:n) - endfunction - - Xpath 8 " X: 8 - call G("error", 1) - try - Xpath 16 " X: 16 - try - call G("error", 2) - Xpath 32 " X: 0 - finally - Xpath 64 " X: 64 - try - call G("interrupt", 3) - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - try - call G("throw", 4) - Xpath 512 " X: 0 - endtry - endtry - endtry - catch /xyz/ - Xpath 1024 " X: 1024 - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 4096 " X: 4096 - call G("aborting error", 5) - Xpath 8192 " X: 0 - Xout "'taken' is" taken "instead of" expected - -endif - -Xcheck 5464 - - -"------------------------------------------------------------------------------- -" Test 67: :throw across :call command {{{1 -" -" On a call command, an exception might be thrown when evaluating the -" function name, during evaluation of the arguments, or when the -" function is being executed. The exception can be caught by the -" caller. -"------------------------------------------------------------------------------- - -XpathINIT - -function! THROW(x, n) - if a:n == 1 - Xpath 1 " X: 1 - elseif a:n == 2 - Xpath 2 " X: 2 - elseif a:n == 3 - Xpath 4 " X: 4 - endif - throw a:x -endfunction - -function! NAME(x, n) - if a:n == 1 - Xpath 8 " X: 0 - elseif a:n == 2 - Xpath 16 " X: 16 - elseif a:n == 3 - Xpath 32 " X: 32 - elseif a:n == 4 - Xpath 64 " X: 64 - endif - return a:x -endfunction - -function! ARG(x, n) - if a:n == 1 - Xpath 128 " X: 0 - elseif a:n == 2 - Xpath 256 " X: 0 - elseif a:n == 3 - Xpath 512 " X: 512 - elseif a:n == 4 - Xpath 1024 " X: 1024 - endif - return a:x -endfunction - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif -endfunction - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - call {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - call {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet error -delfunction F - -Xcheck 212514423 - -" Leave THROW(), NAME(), and ARG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 68: :throw across function calls in expressions {{{1 -" -" On a function call within an expression, an exception might be -" thrown when evaluating the function name, during evaluation of the -" arguments, or when the function is being executed. The exception -" can be caught by the caller. -" -" This test reuses the functions THROW(), NAME(), and ARG() from the -" previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif - return a:x -endfunction - -unlet! var1 var2 var3 var4 - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -if exists("var1") || exists("var2") || exists("var3") || - \ !exists("var4") || var4 != 4711 - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - if exists("var1") - Xout "var1 =" var1 - endif - if exists("var2") - Xout "var2 =" var2 - endif - if exists("var3") - Xout "var3 =" var3 - endif - if !exists("var4") - Xout "var4 unset" - elseif var4 != 4711 - Xout "var4 =" var4 - endif -endif - -unlet! error var1 var2 var3 var4 -delfunction THROW -delfunction NAME -delfunction ARG -delfunction F - -Xcheck 212514423 - -" Tests 69 to 75 were moved to test_trycatch.vim -let Xtest = 76 - - -"------------------------------------------------------------------------------- -" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 -" -" When a function call made during expression evaluation is aborted -" due to an error inside a :try/:endtry region or due to an interrupt -" or a :throw, the expression evaluation is aborted as well. No -" message is displayed for the cancelled expression evaluation. On an -" error not inside :try/:endtry, the expression evaluation continues. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! ERR(n) - let g:taken = g:taken . "E" . a:n - asdf - endfunction - - function! ERRabort(n) abort - let g:taken = g:taken . "A" . a:n - asdf - endfunction " returns -1; may cause follow-up msg for illegal var/func name - - function! WRAP(n, arg) - let g:taken = g:taken . "W" . a:n - let g:saved_errmsg = v:errmsg - return arg - endfunction - - function! INT(n) - let g:taken = g:taken . "I" . a:n - "INTERRUPT9 - let dummy = 0 - endfunction - - function! THR(n) - let g:taken = g:taken . "T" . a:n - throw "should not be caught" - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - function! MSG(n) - let g:taken = g:taken . "M" . a:n - let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg - let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" - if errmsg !~ msgptn - let g:taken = g:taken . "x" - Xout "Expr" a:n.": Unexpected message:" v:errmsg - endif - let v:errmsg = "" - let g:saved_errmsg = "" - endfunction - - let v:errmsg = "" - - try - let t = 1 - XloopINIT 1 2 - while t <= 9 - Xloop 1 " X: 511 - try - if t == 1 - let v{ERR(t) + CONT(t)} = 0 - elseif t == 2 - let v{ERR(t) + CONT(t)} - elseif t == 3 - let var = exists('v{ERR(t) + CONT(t)}') - elseif t == 4 - unlet v{ERR(t) + CONT(t)} - elseif t == 5 - function F{ERR(t) + CONT(t)}() - endfunction - elseif t == 6 - function F{ERR(t) + CONT(t)} - elseif t == 7 - let var = exists('*F{ERR(t) + CONT(t)}') - elseif t == 8 - delfunction F{ERR(t) + CONT(t)} - elseif t == 9 - let var = ERR(t) + CONT(t) - endif - catch /asdf/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been thrown after the original error. - let v:errmsg = "" - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 10 - XloopINIT 1024 2 - while t <= 18 - Xloop 1 " X: 1024 * 511 - try - if t == 10 - let v{INT(t) + CONT(t)} = 0 - elseif t == 11 - let v{INT(t) + CONT(t)} - elseif t == 12 - let var = exists('v{INT(t) + CONT(t)}') - elseif t == 13 - unlet v{INT(t) + CONT(t)} - elseif t == 14 - function F{INT(t) + CONT(t)}() - endfunction - elseif t == 15 - function F{INT(t) + CONT(t)} - elseif t == 16 - let var = exists('*F{INT(t) + CONT(t)}') - elseif t == 17 - delfunction F{INT(t) + CONT(t)} - elseif t == 18 - let var = INT(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ - " An error exception has been triggered after the interrupt. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard interrupt - endtry - endwhile - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 19 - XloopINIT 1048576 2 - while t <= 27 - Xloop 1 " X: 1048576 * 511 - try - if t == 19 - let v{THR(t) + CONT(t)} = 0 - elseif t == 20 - let v{THR(t) + CONT(t)} - elseif t == 21 - let var = exists('v{THR(t) + CONT(t)}') - elseif t == 22 - unlet v{THR(t) + CONT(t)} - elseif t == 23 - function F{THR(t) + CONT(t)}() - endfunction - elseif t == 24 - function F{THR(t) + CONT(t)} - elseif t == 25 - let var = exists('*F{THR(t) + CONT(t)}') - elseif t == 26 - delfunction F{THR(t) + CONT(t)} - elseif t == 27 - let var = THR(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been triggered after the :throw. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard exception - endtry - endwhile - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - let v{ERR(28) + CONT(28)} = 0 - call MSG(28) - let v{ERR(29) + CONT(29)} - call MSG(29) - let var = exists('v{ERR(30) + CONT(30)}') - call MSG(30) - unlet v{ERR(31) + CONT(31)} - call MSG(31) - function F{ERR(32) + CONT(32)}() - endfunction - call MSG(32) - function F{ERR(33) + CONT(33)} - call MSG(33) - let var = exists('*F{ERR(34) + CONT(34)}') - call MSG(34) - delfunction F{ERR(35) + CONT(35)} - call MSG(35) - let var = ERR(36) + CONT(36) - call MSG(36) - - let saved_errmsg = "" - - let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 - call MSG(37) - let v{WRAP(38, ERRabort(38)) + CONT(38)} - call MSG(38) - let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') - call MSG(39) - unlet v{WRAP(40, ERRabort(40)) + CONT(40)} - call MSG(40) - function F{WRAP(41, ERRabort(41)) + CONT(41)}() - endfunction - call MSG(41) - function F{WRAP(42, ERRabort(42)) + CONT(42)} - call MSG(42) - let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') - call MSG(43) - delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} - call MSG(44) - let var = ERRabort(45) + CONT(45) - call MSG(45) - - Xpath 1073741824 " X: 1073741824 - - let expected = "" - \ . "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" - \ . "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" - \ . "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" - \ . "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" - \ . "E34C34M34E35C35M35E36C36M36" - \ . "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" - \ . "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" - - if taken != expected - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, - \ '\(.*\)E3C3M3x\(.*\)E30C30M30x\(.*\)A39C39M39x\(.*\)', - \ '\1E3M3\2E30C30M30\3A39C39M39\4', - \ "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! v var saved_errmsg taken expected - call delete(WA_t5) - call delete(WA_t14) - call delete(WA_t23) - unlet! WA_t5 WA_t14 WA_t23 - delfunction WA_t5 - delfunction WA_t14 - delfunction WA_t23 - -endif - -Xcheck 1610087935 - - -"------------------------------------------------------------------------------- -" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 -" -" When a function call made during evaluation of an expression in -" braces as part of a function name after ":function" is aborted due -" to an error inside a :try/:endtry region or due to an interrupt or -" a :throw, the expression evaluation is aborted as well, and the -" function definition is ignored, skipping all commands to the -" ":endfunction". On an error not inside :try/:endtry, the expression -" evaluation continues and the function gets defined, and can be -" called and deleted. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 4 - -function! ERR() abort - Xloop 1 " X: 1 + 4 + 16 + 64 - asdf -endfunction " returns -1 - -function! OK() - Xloop 2 " X: 2 * (1 + 4 + 16) - let v:errmsg = "" - return 0 -endfunction - -let v:errmsg = "" - -Xpath 4096 " X: 4096 -function! F{1 + ERR() + OK()}(arg) - " F0 should be defined. - if exists("a:arg") && a:arg == "calling" - Xpath 8192 " X: 8192 - else - Xpath 16384 " X: 0 - endif -endfunction -if v:errmsg != "" - Xpath 32768 " X: 0 -endif -XloopNEXT - -Xpath 65536 " X: 65536 -call F{1 + ERR() + OK()}("calling") -if v:errmsg != "" - Xpath 131072 " X: 0 -endif -XloopNEXT - -Xpath 262144 " X: 262144 -delfunction F{1 + ERR() + OK()} -if v:errmsg != "" - Xpath 524288 " X: 0 -endif -XloopNEXT - -try - while 1 - let caught = 0 - try - Xpath 1048576 " X: 1048576 - function! G{1 + ERR() + OK()}(arg) - " G0 should not be defined, and the function body should be - " skipped. - if exists("a:arg") && a:arg == "calling" - Xpath 2097152 " X: 0 - else - Xpath 4194304 " X: 0 - endif - " Use an unmatched ":finally" to check whether the body is - " skipped when an error occurs in ERR(). This works whether or - " not the exception is converted to an exception. - finally - Xpath 8388608 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped" - " Discard the aborting error or exception, and break the - " while loop. - break - " End the try conditional and start a new one to avoid - " ":catch after :finally" errors. - endtry - try - Xpath 16777216 " X: 0 - endfunction - - " When the function was not defined, this won't be reached - whether - " the body was skipped or not. When the function was defined, it - " can be called and deleted here. - Xpath 33554432 " X: 0 - Xout "G0() has been defined" - XloopNEXT - try - call G{1 + ERR() + OK()}("calling") - catch /.*/ - Xpath 67108864 " X: 0 - endtry - Xpath 134217728 " X: 0 - XloopNEXT - try - delfunction G{1 + ERR() + OK()} - catch /.*/ - Xpath 268435456 " X: 0 - endtry - catch /asdf/ - " Jumped to when the function is not defined and the body is - " skipped. - let caught = 1 - catch /.*/ - Xpath 536870912 " X: 0 - finally - if !caught && !$VIMNOERRTHROW - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry " jumped to when the body is not skipped - endwhile -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped, exception caught" - Xout v:exception "in" v:throwpoint -endtry - -Xcheck 1388671 - - -"------------------------------------------------------------------------------- -" Test 78: Messages on parsing errors in expression evaluation {{{1 -" -" When an expression evaluation detects a parsing error, an error -" message is given and converted to an exception, and the expression -" evaluation is aborted. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! F(n) - let g:taken = g:taken . "F" . a:n - endfunction - - function! MSG(n, enr, emsg) - let g:taken = g:taken . "M" . a:n - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - if v:errmsg == "" - Xout "Expr" a:n.": Message missing." - let g:taken = g:taken . "x" - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Expr" a:n.": Unexpected message:" v:errmsg - Xout "Expected: " . a:enr . ': ' . a:emsg - let g:taken = g:taken . "X" - endif - endif - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - let v:errmsg = "" - XloopINIT 1 2 - - try - let t = 1 - while t <= 14 - let g:taken = g:taken . "T" . t - let v:errmsg = "" - try - let caught = 0 - if t == 1 - let v{novar + CONT(t)} = 0 - elseif t == 2 - let v{novar + CONT(t)} - elseif t == 3 - let var = exists('v{novar + CONT(t)}') - elseif t == 4 - unlet v{novar + CONT(t)} - elseif t == 5 - function F{novar + CONT(t)}() - endfunction - elseif t == 6 - function F{novar + CONT(t)} - elseif t == 7 - let var = exists('*F{novar + CONT(t)}') - elseif t == 8 - delfunction F{novar + CONT(t)} - elseif t == 9 - echo novar + CONT(t) - elseif t == 10 - echo v{novar + CONT(t)} - elseif t == 11 - echo F{novar + CONT(t)} - elseif t == 12 - let var = novar + CONT(t) - elseif t == 13 - let var = v{novar + CONT(t)} - elseif t == 14 - let var = F{novar + CONT(t)}() - endif - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if t <= 8 && t != 3 && t != 7 - call MSG(t, 'E475', 'Invalid argument\>') - else - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(t, 'E15', "Invalid expression") - else - call MSG(t, 'E121', "Undefined variable") - endif - endif - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xloop 1 " X: 0 - Xout t.":" v:exception "in" ExtraVimThrowpoint() - endtry - - function! T(n, expr, enr, emsg) - try - let g:taken = g:taken . "T" . a:n - let v:errmsg = "" - try - let caught = 0 - execute "let var = " . a:expr - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(a:n, 'E15', "Invalid expression") - else - call MSG(a:n, a:enr, a:emsg) - endif - XloopNEXT - " Discard an aborting error: - return - endtry - catch /.*/ - Xloop 1 " X: 0 - Xout a:n.":" v:exception "in" ExtraVimThrowpoint() - endtry - endfunction - - call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") - call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") - call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") - call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") - call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") - call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") - call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") - call T(22, '1 2 + CONT(22)', 'E15', "Invalid expression") - call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") - call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") - call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") - call T(26, '& + CONT(26)', 'E112', "Option name missing") - call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") - - Xpath 134217728 " X: 134217728 - - let expected = "" - \ . "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" - \ . "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" - \ . "T26M26T27M27" - - if taken != expected - Xpath 268435456 " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, '\(.*\)T3M3x\(.*\)', '\1T3M3\2', "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! var caught taken expected - call delete(WA_t5) - unlet! WA_t5 - delfunction WA_t5 - -endif - -Xcheck 134217728 - - -"------------------------------------------------------------------------------- -" Test 79: Throwing one of several errors for the same command {{{1 -" -" When several errors appear in a row (for instance during expression -" evaluation), the first as the most specific one is used when -" throwing an error exception. If, however, a syntax error is -" detected afterwards, this one is used for the error exception. -" On a syntax error, the next command is not executed, on a normal -" error, however, it is (relevant only in a function without the -" "abort" flag). v:errmsg is not set. -" -" If throwing error exceptions is configured off, v:errmsg is always -" set to the latest error message, that is, to the more general -" message or the syntax error, respectively. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 2 - -function! NEXT(cmd) - exec a:cmd . " | Xloop 1" -endfunction - -call NEXT('echo novar') " X: 1 * 1 (checks nextcmd) -XloopNEXT -call NEXT('let novar #') " X: 0 * 2 (skips nextcmd) -XloopNEXT -call NEXT('unlet novar #') " X: 0 * 4 (skips nextcmd) -XloopNEXT -call NEXT('let {novar}') " X: 0 * 8 (skips nextcmd) -XloopNEXT -call NEXT('unlet{ novar}') " X: 0 * 16 (skips nextcmd) - -function! EXEC(cmd) - exec a:cmd -endfunction - -function! MATCH(expected, msg, enr, emsg) - let msg = a:msg - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let msg = ":" . msg - endif - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if msg !~ '^'.a:enr.':' || (english && msg !~ a:emsg) - let match = 0 - if a:expected " no match although expected - if a:msg == "" - Xout "Message missing." - else - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected:" a:enr . ": " . a:emsg - endif - endif - else - let match = 1 - if !a:expected " match although not expected - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected none." - endif - endif - return match -endfunction - -try - - while 1 " dummy loop - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC('echo novar') " normal error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 32 " X: 32 - if !caught - if !$VIMNOERRTHROW - Xpath 64 " X: 0 - endif - elseif !MATCH(1, thrmsg, 'E121', "Undefined variable") - \ || v:errmsg != "" - Xpath 128 " X: 0 - endif - if !caught && !MATCH(1, v:errmsg, 'E15', "Invalid expression") - Xpath 256 " X: 0 - endif - break " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 512 " X: 512 - let cmd = "let" - XloopINIT 1024 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' novar #') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 1024 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if cmd == "let" - let match = MATCH(0, thrmsg, 'E121', "Undefined variable") - elseif cmd == "unlet" - let match = MATCH(0, thrmsg, 'E108', "No such variable") - endif - if match " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E488', "Trailing characters") - \|| v:errmsg != "" - " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E488', "Trailing characters") - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 1048576 " X: 1048576 - let cmd = "let" - XloopINIT 2097152 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' {novar}') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 2097152 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if MATCH(0, thrmsg, 'E121', "Undefined variable") " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E475', 'Invalid argument\>') - \ || v:errmsg != "" " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E475', 'Invalid argument\>') - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -unlet! next_command thrmsg match -delfunction NEXT -delfunction EXEC -delfunction MATCH - -Xcheck 70288929 - - -"------------------------------------------------------------------------------- -" Test 80: Syntax error in expression for illegal :elseif {{{1 -" -" If there is a syntax error in the expression after an illegal -" :elseif, an error message is given (or an error exception thrown) -" for the illegal :elseif rather than the expression error. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -let v:errmsg = "" -if 0 -else -elseif 1 ||| 2 -endif -Xpath 1 " X: 1 -if !MSG('E584', ":elseif after :else") - Xpath 2 " X: 0 -endif - -let v:errmsg = "" -if 1 -else -elseif 1 ||| 2 -endif -Xpath 4 " X: 4 -if !MSG('E584', ":elseif after :else") - Xpath 8 " X: 0 -endif - -let v:errmsg = "" -elseif 1 ||| 2 -Xpath 16 " X: 16 -if !MSG('E582', ":elseif without :if") - Xpath 32 " X: 0 -endif - -let v:errmsg = "" -while 1 - elseif 1 ||| 2 -endwhile -Xpath 64 " X: 64 -if !MSG('E582', ":elseif without :if") - Xpath 128 " X: 0 -endif - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 0 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 1 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - elseif 1 ||| 2 - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - while 1 - elseif 1 ||| 2 - endwhile - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -Xpath 16777216 " X: 16777216 - -unlet! caught -delfunction MSG - -Xcheck 17895765 - - -"------------------------------------------------------------------------------- -" Test 81: Discarding exceptions after an error or interrupt {{{1 -" -" When an exception is thrown from inside a :try conditional without -" :catch and :finally clauses and an error or interrupt occurs before -" the :endtry is reached, the exception is discarded. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - throw "arrgh" - Xpath 4 " X: 0 -" if 1 - Xpath 8 " X: 0 - " error after :throw: missing :endif - endtry - Xpath 16 " X: 0 - catch /arrgh/ - Xpath 32 " X: 0 - endtry - Xpath 64 " X: 0 -endif - -if ExtraVim() - try - Xpath 128 " X: 128 - try - Xpath 256 " X: 256 - throw "arrgh" - Xpath 512 " X: 0 - endtry " INTERRUPT - Xpath 1024 " X: 0 - catch /arrgh/ - Xpath 2048 " X: 0 - endtry - Xpath 4096 " X: 0 -endif - -Xcheck 387 - +" Following tests were moved to test_vimscript.vim: +" 1-24, 27-31, 34-40, 49-50, 52-68, 76-81, 87 +" Following tests were moved to test_trycatch.vim: +" 25-26, 32-33, 41-48, 51, 69-75 +let Xtest = 82 "------------------------------------------------------------------------------- " Test 82: Ignoring :catch clauses after an error or interrupt {{{1 diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 0fd65e8f5a..cae71e10f3 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -1,5 +1,6 @@ " Test argument list commands +source check.vim source shared.vim source term_util.vim @@ -550,11 +551,9 @@ func Test_argdo() bwipe Xa.c Xb.c Xc.c endfunc -" Test for quiting Vim with unedited files in the argument list +" Test for quitting Vim with unedited files in the argument list func Test_quit_with_arglist() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('', {'rows': 6}) call term_sendkeys(buf, ":set nomore\n") call term_sendkeys(buf, ":args a b c\n") diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 8723a0a38d..431908e95c 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -48,6 +48,11 @@ func Test_assert_equal() call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0]) call remove(v:errors, 0) + + " special characters are escaped + call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x') + call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_assert_equal_dict() @@ -146,6 +151,14 @@ func Test_assert_exception() try nocommand catch + call assert_equal(1, assert_exception('E12345:')) + endtry + call assert_match("Expected 'E12345:' but got 'Vim:E492: ", v:errors[0]) + call remove(v:errors, 0) + + try + nocommand + catch try " illegal argument, get NULL for error call assert_equal(1, assert_exception([])) @@ -153,6 +166,10 @@ func Test_assert_exception() call assert_equal(0, assert_exception('E730:')) endtry endtry + + call assert_equal(1, assert_exception('E492:')) + call assert_match('v:exception is not set', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_wrong_error_type() @@ -202,6 +219,14 @@ func Test_assert_fail_fails() call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0]) call remove(v:errors, 0) + call assert_equal(1, assert_fails('xxx', ['E9876'])) + call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876'])) + call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_fails('echo', '', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) @@ -209,6 +234,41 @@ func Test_assert_fail_fails() call assert_equal(1, 'echo'->assert_fails('', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) + + try + call assert_equal(1, assert_fails('xxx', [])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', ['1', '2', '3'])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', #{one: 1})) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp')) + catch + let exp = v:exception + endtry + call assert_match("E1115: assert_fails() fourth argument must be a number", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123)) + catch + let exp = v:exception + endtry + call assert_match("E1116: assert_fails() fifth argument must be a string", exp) endfunc func Test_assert_fails_in_try_block() diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 4229095f9f..a8810047a0 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -122,9 +122,7 @@ endfunc func Test_multibyte() " using an invalid character should not cause a crash set wic - " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet - " call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') - call assert_fails('tc *', has('win32') ? 'E480:' : 'E472:') + call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') set nowic endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 8c15249f97..50904bab34 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -175,9 +175,7 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - " Todo: check for E937 generated first - " call assert_fails('edit bb.txt', 'E937:') - call assert_fails('edit bb.txt', 'E517:') + call assert_fails('edit bb.txt', ['E937:', 'E517:']) autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -533,6 +531,8 @@ func Test_augroup_warning() redir END call assert_notmatch("W19:", res) au! VimEnter + + call assert_fails('augroup!', 'E471:') endfunc func Test_BufReadCmdHelp() @@ -2931,7 +2931,7 @@ func Test_BufDelete_changebuf() augroup END let save_cpo = &cpo set cpo+=f - call assert_fails('r Xfile', 'E484:') + call assert_fails('r Xfile', ['E812:', 'E484:']) call assert_equal('somefile', @%) let &cpo = save_cpo augroup TestAuCmd diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim index ce2bfe72bc..7eff818732 100644 --- a/src/nvim/testdir/test_backup.vim +++ b/src/nvim/testdir/test_backup.vim @@ -1,5 +1,7 @@ " Tests for the backup function +source check.vim + func Test_backup() set backup backupdir=. backupskip= new @@ -56,3 +58,19 @@ func Test_backup2_backupcopy() call delete(f) set backup&vim backupdir&vim backupcopy&vim backupskip&vim endfunc + +" Test for using a non-existing directory as a backup directory +func Test_non_existing_backupdir() + throw 'Skipped: Nvim auto-creates backup directory' + CheckNotBSD + let save_backup = &backupdir + set backupdir=./non_existing_dir + call writefile(['line1'], 'Xfile') + new Xfile + " TODO: write doesn't fail in Cirrus FreeBSD CI test + call assert_fails('write', 'E510:') + let &backupdir = save_backup + call delete('Xfile') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index 70529c14d5..046acb81e1 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -88,6 +88,7 @@ func Test_blob_get_range() call assert_equal(0z0011223344, b[:]) call assert_equal(0z0011223344, b[:-1]) call assert_equal(0z, b[5:6]) + call assert_equal(0z0011, b[-10:1]) endfunc func Test_blob_get() @@ -208,6 +209,7 @@ func Test_blob_add() call assert_equal(0z001122, b) call add(b, '51') call assert_equal(0z00112233, b) + call assert_equal(1, add(v:_null_blob, 0x22)) call assert_fails('call add(b, [9])', 'E745:') call assert_fails('call add("", 0x01)', 'E897:') @@ -250,6 +252,7 @@ func Test_blob_func_remove() call assert_fails("call remove(b, 3, 2)", 'E979:') call assert_fails("call remove(1, 0)", 'E896:') call assert_fails("call remove(b, b)", 'E974:') + call assert_fails("call remove(b, 1, [])", 'E745:') call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:') " Translated from v8.2.3284 @@ -272,6 +275,7 @@ endfunc " filter() item in blob func Test_blob_filter() + call assert_equal(v:_null_blob, filter(v:_null_blob, '0')) call assert_equal(0z, filter(0zDEADBEEF, '0')) call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE')) call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE')) @@ -297,6 +301,9 @@ func Test_blob_index() call assert_equal(3, 0z11110111->index(0x11, 2)) call assert_equal(2, index(0z11111111, 0x11, -2)) call assert_equal(3, index(0z11110111, 0x11, -2)) + call assert_equal(0, index(0z11110111, 0x11, -10)) + call assert_fails("echo index(0z11110111, 0x11, [])", 'E745:') + call assert_equal(-1, index(v:_null_blob, 1)) call assert_fails('call index("asdf", 0)', 'E897:') endfunc @@ -313,6 +320,9 @@ func Test_blob_insert() call assert_fails('call insert(b, -1)', 'E475:') call assert_fails('call insert(b, 257)', 'E475:') call assert_fails('call insert(b, 0, [9])', 'E745:') + call assert_fails('call insert(b, 0, -20)', 'E475:') + call assert_fails('call insert(b, 0, 20)', 'E475:') + call assert_fails('call insert(b, [])', 'E745:') call assert_equal(0, insert(v:_null_blob, 0x33)) " Translated from v8.2.3284 diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index a37751e748..995683c68c 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -4,11 +4,11 @@ " while the test is run, the breakindent caching gets in its way. " It helps to change the tabstop setting and force a redraw (e.g. see " Test_breakindent08()) -if !exists('+breakindent') - throw 'Skipped: breakindent option not supported' -endif +source check.vim +CheckOption breakindent source view_util.vim +source screendump.vim let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" @@ -422,6 +422,7 @@ func Test_breakindent11() 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=') + call assert_equal(4, strdisplaywidth("\t", 4)) endfunc func Test_breakindent11_vartabs() @@ -876,17 +877,164 @@ endfunc func Test_window_resize_with_linebreak() new 53vnew - set linebreak - set showbreak=>> - set breakindent - set breakindentopt=shift:4 + setl linebreak + setl showbreak=>> + setl breakindent + setl breakindentopt=shift:4 call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a") redraw! call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14)) vertical resize 52 redraw! call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14)) + set linebreak& showbreak& breakindent& breakindentopt& %bw! endfunc +func Test_cursor_position_with_showbreak() + CheckScreendump + + let lines =<< trim END + vim9script + &signcolumn = 'yes' + &showbreak = '+ ' + var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff') + repeat('x', &columns - leftcol - 1)->setline(1) + 'second line'->setline(2) + END + call writefile(lines, 'XscriptShowbreak') + let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6}) + + call term_sendkeys(buf, "AX") + call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptShowbreak') +endfunc + +func Test_no_spurious_match() + let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50)) + call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls') + let @/ = '\%>3v[y]' + redraw! + call searchcount().total->assert_equal(1) + " cleanup + set hls&vim + let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + bwipeout! +endfunc + +func Test_no_extra_indent() + call s:test_windows('setl breakindent breakindentopt=list:-1,min:10') + %d + let &l:formatlistpat='^\s*\d\+\.\s\+' + let text = 'word ' + let len = text->strcharlen() + let line1 = text->repeat((winwidth(0) / len) * 2) + let line2 = repeat(' ', 2) .. '1. ' .. line1 + call setline(1, [line2]) + redraw! + " 1) matches formatlist pattern, so indent + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) change formatlist pattern + " -> indent adjusted + let &l:formatlistpat='^\s*\d\+\.' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + " 3) no local formatlist pattern, + " so use global one -> indent + let g_flp = &g:flp + let &g:formatlistpat='^\s*\d\+\.\s\+' + let &l:formatlistpat='' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + let &g:flp = g_flp + let &l:formatlistpat='^\s*\d\+\.' + " 4) add something in front, no additional indent + norm! gg0 + exe ":norm! 5iword \<esc>" + redraw! + let expect = [ + \ "word word word word ", + \ "word 1. word word ", + \ "word word word word ", + \ "word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + +func Test_breakindent_column() + " restore original + let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + call s:test_windows('setl breakindent breakindentopt=column:10') + redraw! + " 1) default: does not indent, too wide :( + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ "qrstuvwxyzABCDEFGHIJ", + \ "KLMNOP " + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) lower min value, so that breakindent works + setl breakindentopt+=min:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + " 3) set shift option -> no influence + setl breakindentopt+=shift:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + " 4) add showbreak value + setl showbreak=++ + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " ++qrstuvwx", + \ " ++yzABCDEF", + \ " ++GHIJKLMN", + \ " ++OP " + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 4def3b5df9..27c2d5d442 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -76,7 +76,7 @@ func Test_bunload_with_offset() let caught_E90 = 1 endtry call assert_equal(1, caught_E90) - call assert_fails('$bunload', 'E515:') + call assert_fails('$bunload', 'E90:') endfunc " Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified @@ -282,7 +282,7 @@ func Test_goto_buf_with_confirm() call assert_equal(1, &modified) call assert_equal('', @%) call feedkeys('y', 'L') - call assert_fails('confirm b Xfile', 'E37:') + call assert_fails('confirm b Xfile', ['', 'E37:']) call assert_equal(1, &modified) call assert_equal('', @%) call feedkeys('n', 'L') diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index 3b5bcbce89..2867f13cbc 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -2,6 +2,7 @@ source shared.vim source screendump.vim +source check.vim func Test_setbufline_getbufline() new @@ -18,13 +19,25 @@ func Test_setbufline_getbufline() call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, setbufline(b, 5, 'x')) call assert_equal(1, setbufline(b, 5, ['x'])) + call assert_equal(1, setbufline(b, 5, [])) + call assert_equal(1, setbufline(b, 5, v:_null_list)) + + call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1)) call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, []->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, v:_null_list->setbufline(bufnr('$') + 1, 1)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) call assert_equal(['d'], getbufline(b, 4)) call assert_equal(['e'], getbufline(b, 5)) call assert_equal([], getbufline(b, 6)) + call assert_equal([], getbufline(b, 2, 1)) exe "bwipe! " . b endfunc @@ -82,9 +95,29 @@ func Test_appendbufline() call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, appendbufline(b, -1, 'x')) call assert_equal(1, appendbufline(b, -1, ['x'])) + call assert_equal(1, appendbufline(b, -1, [])) + call assert_equal(1, appendbufline(b, -1, v:_null_list)) + + call assert_equal(1, appendbufline(b, 4, 'x')) call assert_equal(1, appendbufline(b, 4, ['x'])) + call assert_equal(1, appendbufline(b, 4, [])) + call assert_equal(1, appendbufline(b, 4, v:_null_list)) + + call assert_equal(1, appendbufline(1234, 1, 'x')) call assert_equal(1, appendbufline(1234, 1, ['x'])) + call assert_equal(1, appendbufline(1234, 1, [])) + call assert_equal(1, appendbufline(1234, 1, v:_null_list)) + + call assert_equal(0, appendbufline(b, 1, [])) + call assert_equal(0, appendbufline(b, 1, v:_null_list)) + call assert_equal(1, appendbufline(b, 3, [])) + call assert_equal(1, appendbufline(b, 3, v:_null_list)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, appendbufline(b, 3, ['d', 'e'])) call assert_equal(['c'], getbufline(b, 3)) call assert_equal(['d'], getbufline(b, 4)) @@ -130,9 +163,8 @@ func Test_deletebufline() endfunc func Test_appendbufline_redraw() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let lines =<< trim END new foo let winnr = 'foo'->bufwinnr() diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index d6d44d1901..2a2437f542 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -5,7 +5,7 @@ source check.vim func Test_cd_large_path() " This used to crash with a heap write overflow. - call assert_fails('cd ' . repeat('x', 5000), 'E472:') + call assert_fails('cd ' . repeat('x', 5000), 'E344:') endfunc func Test_cd_up_and_down() @@ -45,9 +45,7 @@ func Test_cd_minus() call assert_equal(path, getcwd()) " Test for :cd - after a failed :cd - " v8.2.1183 is not ported yet - " call assert_fails('cd /nonexistent', 'E344:') - call assert_fails('cd /nonexistent', 'E472:') + call assert_fails('cd /nonexistent', 'E344:') call assert_equal(path, getcwd()) cd - call assert_equal(path_dotdot, getcwd()) @@ -68,30 +66,6 @@ func Test_cd_minus() call delete('Xresult') endfunc -func Test_cd_with_cpo_chdir() - e Xfoo - call setline(1, 'foo') - let path = getcwd() - " set cpo+=. - - " :cd should fail when buffer is modified and 'cpo' contains dot. - " call assert_fails('cd ..', 'E747:') - call assert_equal(path, getcwd()) - - " :cd with exclamation mark should succeed. - cd! .. - call assert_notequal(path, getcwd()) - - " :cd should succeed when buffer has been written. - w! - exe 'cd ' .. fnameescape(path) - call assert_equal(path, getcwd()) - - call delete('Xfoo') - set cpo& - bw! -endfunc - " Test for chdir() func Test_chdir_func() let topdir = getcwd() @@ -127,7 +101,7 @@ func Test_chdir_func() call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) " Error case - call assert_fails("call chdir('dir-abcd')", 'E472:') + call assert_fails("call chdir('dir-abcd')", 'E344:') silent! let d = chdir("dir_abcd") call assert_equal("", d) " Should not crash diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index d386d74f8d..54e0a62ce5 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -43,36 +43,6 @@ func Test_charsearch() enew! endfunc -" Test for t,f,F,T movement commands and 'cpo-;' setting -func Test_search_cmds() - enew! - call append(0, ["aaa two three four", " zzz", "yyy ", - \ "bbb yee yoo four", "ccc two three four", - \ "ddd yee yoo four"]) - set cpo-=; - 1 - normal! 0tt;D - 2 - normal! 0fz;D - 3 - normal! $Fy;D - 4 - normal! $Ty;D - set cpo+=; - 5 - normal! 0tt;;D - 6 - normal! $Ty;;D - - call assert_equal('aaa two', getline(1)) - call assert_equal(' z', getline(2)) - call assert_equal('y', getline(3)) - call assert_equal('bbb y', getline(4)) - call assert_equal('ccc', getline(5)) - call assert_equal('ddd yee y', getline(6)) - enew! -endfunc - " Test for character search in virtual edit mode with <Tab> func Test_csearch_virtualedit() new @@ -81,7 +51,7 @@ func Test_csearch_virtualedit() normal! tb call assert_equal([0, 1, 2, 6], getpos('.')) set virtualedit& - close! + bw! endfunc " Test for character search failure in latin1 encoding @@ -95,7 +65,34 @@ func Test_charsearch_latin1() call assert_beeps('normal $Fz') call assert_beeps('normal $Tx') let &encoding = save_enc - close! + bw! +endfunc + +" Test for using character search to find a multibyte character with composing +" characters. +func Test_charsearch_composing_char() + new + call setline(1, "one two thq\u0328\u0301r\u0328\u0301ree") + call feedkeys("fr\u0328\u0301", 'xt') + call assert_equal([0, 1, 16, 0, 12], getcurpos()) + + " use character search with a multi-byte character followed by a + " non-composing character + call setline(1, "abc deȉf ghi") + call feedkeys("ggcf\u0209\u0210", 'xt') + call assert_equal("\u0210f ghi", getline(1)) + bw! +endfunc + +" Test for character search with 'hkmap' +func Test_charsearch_hkmap() + new + set hkmap + call setline(1, "ùðáâ÷ëòéïçìêöî") + call feedkeys("fë", 'xt') + call assert_equal([0, 1, 11, 0, 6], getcurpos()) + set hkmap& + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index a4ebce5af9..19a92dce3c 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -102,7 +102,7 @@ func Test_client_server() call remote_send(v:servername, ":let g:testvar2 = 75\<CR>") call feedkeys('', 'x') call assert_equal(75, g:testvar2) - call assert_fails('let v = remote_expr(v:servername, "/2")', 'E449:') + call assert_fails('let v = remote_expr(v:servername, "/2")', ['E15:.*/2']) call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call assert_equal('got it', g:myserverid->remote_read(2)) @@ -147,7 +147,7 @@ func Test_client_server() " Edit files in separate tab pages call system(cmd .. ' --remote-tab Xfile1 Xfile2 Xfile3') - call assert_equal('3', remote_expr(name, 'tabpagenr("$")')) + call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))}) call assert_equal('Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])')) eval name->remote_send(":%bw!\<CR>") @@ -182,9 +182,10 @@ func Test_client_server() endif endtry + call assert_fails('call remote_startserver([])', 'E730:') call assert_fails("let x = remote_peek([])", 'E730:') - call assert_fails("let x = remote_read('vim10')", 'E277:') - call assert_fails("call server2client('abc', 'xyz')", 'E258:') + call assert_fails("let x = remote_read('vim10')", ['E573:.*vim10']) + call assert_fails("call server2client('abc', 'xyz')", ['E573:.*abc']) endfunc " Uncomment this line to get a debugging log diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index f37e8be59a..c00172ed68 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -89,6 +89,13 @@ func Test_complete_wildmenu() call assert_equal('"e Xtestfile3 Xtestfile4', @:) cd - + cnoremap <expr> <F2> wildmenumode() + call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cd Xdir1/0', @:) + call feedkeys(":e Xdir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"e Xdir1/Xdir2/1', @:) + cunmap <F2> + " cleanup %bwipe call delete('Xdir1/Xdir2/Xtestfile4') @@ -854,7 +861,7 @@ func Test_cmdline_complete_bang() endif endfunc -funct Test_cmdline_complete_languages() +func Test_cmdline_complete_languages() let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '') call assert_equal(lang, v:lc_time) @@ -891,10 +898,8 @@ endfunc func Test_cmdline_complete_env_variable() let $X_VIM_TEST_COMPLETE_ENV = 'foo' - call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:) - unlet $X_VIM_TEST_COMPLETE_ENV endfunc @@ -1074,9 +1079,25 @@ func Test_cmdline_complete_various() call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt') call assert_equal('"e `a1b2c', @:) - " completion for the expression register - call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') - call assert_equal('"float2nr("', @=) + " completion for :language command with an invalid argument + call feedkeys(":language dummy \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"language dummy \t", @:) + + " completion for commands after a :global command + call feedkeys(":g/a\\xb/clearj\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + + " completion with ambiguous user defined commands + com TCmd1 echo 'TCmd1' + com TCmd2 echo 'TCmd2' + call feedkeys(":TCmd \t\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd ', @:) + delcom TCmd1 + delcom TCmd2 + + " completion after a range followed by a pipe (|) character + call feedkeys(":1,10 | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"1,10 | chistory', @:) endfunc func Test_cmdline_write_alternatefile() @@ -1133,7 +1154,7 @@ func Test_cmdline_search_range() call assert_equal('B', getline(2)) let @/ = 'apple' - call assert_fails('\/print', 'E486:') + call assert_fails('\/print', ['E486:.*apple']) bwipe! endfunc @@ -1249,6 +1270,11 @@ func Test_verbosefile() let log = readfile('Xlog') call assert_match("foo\nbar", join(log, "\n")) call delete('Xlog') + call mkdir('Xdir') + if !has('win32') " FIXME: no error on Windows, libuv bug? + call assert_fails('set verbosefile=Xdir', ['E484:.*Xdir', 'E474:']) + endif + call delete('Xdir', 'd') endfunc func Test_verbose_option() @@ -1499,7 +1525,7 @@ func Test_cmdwin_jump_to_win() call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') new set modified - call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') + call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', ['E37:', 'E162:']) close! call feedkeys("q/:close\<CR>", "xt") call assert_equal(1, winnr('$')) @@ -1513,13 +1539,7 @@ endfunc func Test_cmdwin_tabpage() tabedit - " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. - " v8.2.1183 also isn't ported yet, so we also can't assert E11 directly. - " For now, assert E11 and E492 separately. When v8.2.1183 is ported, the - " assert for E492 will fail and this workaround should be removed. - " call assert_fails("silent norm q/g :I\<Esc>", 'E11:') - call assert_fails("silent norm q/g ", 'E11:') - call assert_fails("silent norm q/g :I\<Esc>", 'E492:') + call assert_fails("silent norm q/g :I\<Esc>", 'E11:') tabclose! endfunc @@ -1682,16 +1702,16 @@ func Test_wildmode() " Test for wildmode=longest with 'fileignorecase' set set wildmode=longest set fileignorecase - argadd AA AAA AAAA - call feedkeys(":buffer \t\<C-B>\"\<CR>", 'xt') - call assert_equal('"buffer AA', @:) + argadd AAA AAAA AAAAA + call feedkeys(":buffer a\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"buffer AAA', @:) set fileignorecase& " Test for listing files with wildmode=list set wildmode=list let g:Sline = '' call feedkeys(":b A\t\t\<F2>\<C-B>\"\<CR>", 'xt') - call assert_equal('AA AAA AAAA', g:Sline) + call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('"b A', @:) %argdelete diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 0d064617a5..7f19085b16 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -231,6 +231,14 @@ func Test_const_with_special_variables() call assert_fails('const &filetype = "vim"', 'E996:') call assert_fails('const &l:filetype = "vim"', 'E996:') call assert_fails('const &g:encoding = "utf-8"', 'E996:') + + call assert_fails('const [a, $CONST_FOO] = [369, "abc"]', 'E996:') + call assert_equal(369, a) + call assert_equal(v:null, getenv("CONST_FOO")) + + call assert_fails('const [b; $CONST_FOO] = [246, 2, "abc"]', 'E996:') + call assert_equal(246, b) + call assert_equal(v:null, getenv("CONST_FOO")) endfunc func Test_const_with_eval_name() @@ -274,3 +282,5 @@ func Test_lock_depth_is_2() const d2 = #{a: 0, b: lvar, c: 4} let d2.b[1] = 'd' endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cpoptions.vim b/src/nvim/testdir/test_cpoptions.vim new file mode 100644 index 0000000000..ef51d955f1 --- /dev/null +++ b/src/nvim/testdir/test_cpoptions.vim @@ -0,0 +1,927 @@ +" Test for the various 'cpoptions' (cpo) flags + +source check.vim +source shared.vim +source view_util.vim + +" Test for the 'a' flag in 'cpo'. Reading a file should set the alternate +" file name. +func Test_cpo_a() + let save_cpo = &cpo + call writefile(['one'], 'Xfile') + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=a + new + read Xfile + call assert_equal('', @#) + %d + set cpo+=a + read Xfile + call assert_equal('Xfile', @#) + close! + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the 'A' flag in 'cpo'. Writing a file should set the alternate +" file name. +func Test_cpo_A() + let save_cpo = &cpo + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=A + new Xfile1 + write Xfile2 + call assert_equal('', @#) + %bw + call delete('Xfile2') + new Xfile1 + set cpo+=A + write Xfile2 + call assert_equal('Xfile2', @#) + close! + call delete('Xfile2') + let &cpo = save_cpo +endfunc + +" Test for the 'b' flag in 'cpo'. "\|" at the end of a map command is +" recognized as the end of the map. +func Test_cpo_b() + let save_cpo = &cpo + set cpo+=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>\', maparg('<F5>')) + nunmap <F5> + exe "nnoremap <F5> :pwd\<C-V>|let i = 1" + call assert_equal(':pwd|let i = 1', maparg('<F5>')) + nunmap <F5> + set cpo-=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>|let i = 1', maparg('<F5>')) + let &cpo = save_cpo + nunmap <F5> +endfunc + +" Test for the 'B' flag in 'cpo'. A backslash in mappings, abbreviations, user +" commands and menu commands has no special meaning. +func Test_cpo_B() + let save_cpo = &cpo + new + set cpo-=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('ab<BS>d ', getline(1)) + %d + set cpo+=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('abd ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'c' flag in 'cpo'. +func Test_cpo_c() + let save_cpo = &cpo + set cpo+=c + new + call setline(1, ' abababababab') + exe "normal gg/abab\<CR>" + call assert_equal(3, searchcount().total) + set cpo-=c + exe "normal gg/abab\<CR>" + call assert_equal(5, searchcount().total) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'C' flag in 'cpo' (line continuation) +func Test_cpo_C() + let save_cpo = &cpo + call writefile(['let l = [', '\ 1,', '\ 2]'], 'Xfile') + set cpo-=C + source Xfile + call assert_equal([1, 2], g:l) + set cpo+=C + call assert_fails('source Xfile', ['E697:', 'E10:']) + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the 'd' flag in 'cpo' (tags relative to the current file) +func Test_cpo_d() + let save_cpo = &cpo + call mkdir('Xdir') + call writefile(["one\tXfile1\t/^one$/"], 'tags') + call writefile(["two\tXfile2\t/^two$/"], 'Xdir/tags') + set tags=./tags + set cpo-=d + edit Xdir/Xfile + call assert_equal('two', taglist('.*')[0].name) + set cpo+=d + call assert_equal('one', taglist('.*')[0].name) + %bw! + call delete('tags') + call delete('Xdir', 'rf') + set tags& + let &cpo = save_cpo +endfunc + +" Test for the 'D' flag in 'cpo' (digraph after a r, f or t) +func Test_cpo_D() + CheckFeature digraphs + let save_cpo = &cpo + new + set cpo-=D + call setline(1, 'abcdefgh|') + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(9, col('.')) + set cpo+=D + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(1, col('.')) + set cpo-=D + close! + let &cpo = save_cpo +endfunc + +" Test for the 'e' flag in 'cpo' +func Test_cpo_e() + let save_cpo = &cpo + let @a='let i = 45' + set cpo+=e + call feedkeys(":@a\<CR>", 'xt') + call assert_equal(45, i) + set cpo-=e + call feedkeys(":@a\<CR>6\<CR>", 'xt') + call assert_equal(456, i) + let &cpo = save_cpo +endfunc + +" Test for the 'E' flag in 'cpo' with yank, change, delete, etc. operators +func Test_cpo_E() + new + call setline(1, '') + set cpo+=E + " yank an empty line + call assert_beeps('normal "ayl') + " change an empty line + call assert_beeps('normal lcTa') + call assert_beeps('normal 0c0') + " delete an empty line + call assert_beeps('normal D') + call assert_beeps('normal dl') + call assert_equal('', getline(1)) + " change case of an empty line + call assert_beeps('normal gul') + call assert_beeps('normal gUl') + " replace a character + call assert_beeps('normal vrx') + " increment and decrement + call assert_beeps('exe "normal v\<C-A>"') + call assert_beeps('exe "normal v\<C-X>"') + set cpo-=E + close! +endfunc + +" Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name) +func Test_cpo_f() + let save_cpo = &cpo + new + set cpo-=f + read test_cpoptions.vim + call assert_equal('', @%) + %d + set cpo+=f + read test_cpoptions.vim + call assert_equal('test_cpoptions.vim', @%) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'F' flag in 'cpo' (write in an empty buffer sets the file name) +func Test_cpo_F() + let save_cpo = &cpo + new + set cpo-=F + write Xfile + call assert_equal('', @%) + call delete('Xfile') + set cpo+=F + write Xfile + call assert_equal('Xfile', @%) + close! + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file) +func Test_cpo_g() + throw 'Skipped: Nvim does not support cpoptions flag "g"' + let save_cpo = &cpo + new test_cpoptions.vim + set cpo-=g + normal 20G + edit + call assert_equal(20, line('.')) + set cpo+=g + edit + call assert_equal(1, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') +func Test_cpo_H() + throw 'Skipped: Nvim does not support cpoptions flag "H"' + let save_cpo = &cpo + new + set cpo-=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a', getline(1)) + set cpo+=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'i' flag in 'cpo' +" Interrupting the reading of a file will leave it modified. + +" Test for the 'I' flag in 'cpo' (deleting autoindent when using arrow keys) +func Test_cpo_I() + let save_cpo = &cpo + new + setlocal autoindent + set cpo+=I + exe "normal i one\<CR>\<Up>" + call assert_equal(' ', getline(2)) + set cpo-=I + %d + exe "normal i one\<CR>\<Up>" + call assert_equal('', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'j' flag in 'cpo' is in the test_join.vim file. + +" Test for the 'J' flag in 'cpo' (two spaces after a sentence) +func Test_cpo_J() + let save_cpo = &cpo + new + set cpo-=J + call setline(1, 'one. two! three? four."'' five.)]') + normal 0 + for colnr in [6, 12, 19, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 19, 12, 6, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + set cpo+=J + normal 0 + for colnr in [12, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 12, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'k' flag in 'cpo'. +" Disable the recognition of raw key codes in mappings, abbreviations, and the +" "to" part of menu commands. + +" TODO: Add a test for the 'K' flag in 'cpo'. +" Don't wait for a key code to complete when it is halfway a mapping. + +" Test for the 'l' flag in 'cpo' (backslash in a [] range) +func Test_cpo_l() + let save_cpo = &cpo + new + call setline(1, ['', "a\tc" .. '\t']) + set cpo-=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([2, 8], [col('.'), virtcol('.')]) + set cpo+=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([4, 10], [col('.'), virtcol('.')]) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') +func Test_cpo_L() + let save_cpo = &cpo + new + set cpo-=L + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo+=L + set list + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) + set nolist + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'm' flag in 'cpo'. +" When included, a showmatch will always wait half a second. When not +" included, a showmatch will wait half a second or until a character is typed. + +" Test for the 'M' flag in 'cpo' (% with escape parenthesis) +func Test_cpo_M() + let save_cpo = &cpo + new + call setline(1, ['( \( )', '\( ( \)']) + + set cpo-=M + call cursor(1, 1) + normal % + call assert_equal(6, col('.')) + call cursor(1, 4) + call assert_beeps('normal %') + call cursor(2, 2) + normal % + call assert_equal(7, col('.')) + call cursor(2, 4) + call assert_beeps('normal %') + + set cpo+=M + call cursor(1, 4) + normal % + call assert_equal(6, col('.')) + call cursor(1, 1) + call assert_beeps('normal %') + call cursor(2, 4) + normal % + call assert_equal(7, col('.')) + call cursor(2, 1) + call assert_beeps('normal %') + + close! + let &cpo = save_cpo +endfunc + +" Test for the 'n' flag in 'cpo' (using number column for wrapped lines) +func Test_cpo_n() + let save_cpo = &cpo + new + call setline(1, repeat('a', &columns)) + setlocal number + set cpo-=n + redraw! + call assert_equal(' aaaa', Screenline(2)) + set cpo+=n + redraw! + call assert_equal('aaaa', Screenline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'o' flag in 'cpo' (line offset to search command) +func Test_cpo_o() + let save_cpo = &cpo + new + call setline(1, ['', 'one', 'two', 'three', 'one', 'two', 'three']) + set cpo-=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(7, line('.')) + set cpo+=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(5, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'O' flag in 'cpo' (overwriting an existing file) +func Test_cpo_O() + let save_cpo = &cpo + new Xfile + call setline(1, 'one') + call writefile(['two'], 'Xfile') + set cpo-=O + call assert_fails('write', 'E13:') + set cpo+=O + write + call assert_equal(['one'], readfile('Xfile')) + close! + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the 'p' flag in 'cpo' is in the test_lispwords.vim file. + +" Test for the 'P' flag in 'cpo' (appending to a file sets the current file +" name) +func Test_cpo_P() + let save_cpo = &cpo + call writefile([], 'Xfile') + new + call setline(1, 'one') + set cpo+=F + set cpo-=P + write >> Xfile + call assert_equal('', @%) + set cpo+=P + write >> Xfile + call assert_equal('Xfile', @%) + close! + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the 'q' flag in 'cpo' (joining multiple lines) +func Test_cpo_q() + let save_cpo = &cpo + new + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo-=q + normal gg4J + call assert_equal(14, col('.')) + %d + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo+=q + normal gg4J + call assert_equal(4, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'r' flag in 'cpo' (redo command with a search motion) +func Test_cpo_r() + let save_cpo = &cpo + new + call setline(1, repeat(['one two three four'], 2)) + set cpo-=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc two three four', getline(2)) + %d + call setline(1, repeat(['one two three four'], 2)) + set cpo+=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc three four', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'R' flag in 'cpo' (clear marks after a filter command) +func Test_cpo_R() + CheckUnix + let save_cpo = &cpo + new + call setline(1, ['three', 'one', 'two']) + set cpo-=R + 3mark r + %!sort + call assert_equal(3, line("'r")) + %d + call setline(1, ['three', 'one', 'two']) + set cpo+=R + 3mark r + %!sort + call assert_equal(0, line("'r")) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 's' flag in 'cpo'. +" Set buffer options when entering the buffer for the first time. If not +" present the options are set when the buffer is created. + +" Test for the 'S' flag in 'cpo' (copying buffer options) +func Test_cpo_S() + let save_cpo = &cpo + new Xfile1 + set noautoindent + new Xfile2 + set cpo-=S + set autoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd p + call assert_equal(1, &autoindent) + set cpo+=S + wincmd p + call assert_equal(1, &autoindent) + set noautoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd t + close! + close! + let &cpo = save_cpo +endfunc + +" Test for the 't' flag in 'cpo' is in the test_tagjump.vim file. + +" Test for the 'u' flag in 'cpo' (Vi-compatible undo) +func Test_cpo_u() + let save_cpo = &cpo + new + set cpo-=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abc', getline(1)) + %d + set cpo+=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abcdefghi', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'v' flag in 'cpo'. +" Backspaced characters remain visible on the screen in Insert mode. + +" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one +" character) +func Test_cpo_w() + throw 'Skipped: Nvim does not support cpoptions flag "w"' + let save_cpo = &cpo + new + set cpo+=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZ are some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZ areYYY some words', getline('.')) + set cpo-=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZare some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZare someYYYwords', getline('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'W' flag in 'cpo' is in the test_writefile.vim file + +" Test for the 'x' flag in 'cpo' (Esc on command-line executes command) +func Test_cpo_x() + let save_cpo = &cpo + set cpo-=x + let i = 1 + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(1, i) + set cpo+=x + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(10, i) + let &cpo = save_cpo +endfunc + +" Test for the 'X' flag in 'cpo' ('R' with a count) +func Test_cpo_X() + let save_cpo = &cpo + new + call setline(1, 'aaaaaa') + set cpo-=X + normal gg4Rx + call assert_equal('xxxxaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyaa', getline(1)) + call setline(1, 'aaaaaa') + set cpo+=X + normal gg4Rx + call assert_equal('xxxxaaaaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyxxxaaaaa', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'y' flag in 'cpo' (repeating a yank command) +func Test_cpo_y() + let save_cpo = &cpo + new + call setline(1, ['one', 'two']) + set cpo-=y + normal ggyy + normal 2G. + call assert_equal("one\n", @") + %d + call setline(1, ['one', 'two']) + set cpo+=y + normal ggyy + normal 2G. + call assert_equal("two\n", @") + close! + let &cpo = save_cpo +endfunc + +" Test for the 'Z' flag in 'cpo' (write! resets 'readonly') +func Test_cpo_Z() + let save_cpo = &cpo + call writefile([], 'Xfile') + new Xfile + setlocal readonly + set cpo-=Z + write! + call assert_equal(0, &readonly) + set cpo+=Z + setlocal readonly + write! + call assert_equal(1, &readonly) + close! + call delete('Xfile') + let &cpo = save_cpo +endfunc + +" Test for the '!' flag in 'cpo' is in the test_normal.vim file + +" Test for displaying dollar when changing text ('$' flag in 'cpoptions') +func Test_cpo_dollar() + throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return '' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + set cpo+=$ + call setline(1, 'one two three') + redraw! + exe "normal c2w\<F2>vim" + call assert_equal('one tw$ three', g:Line) + call assert_equal('vim three', getline(1)) + set cpo-=$ + call test_override('ALL', 0) + delfunc SaveFirstLine + %bw! +endfunc + +" Test for the '%' flag in 'cpo' (parenthesis matching inside strings) +func Test_cpo_percent() + let save_cpo = &cpo + new + call setline(1, ' if (strcmp("ab)cd(", s))') + set cpo-=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(27, col('.')) + normal 27|% + call assert_equal(15, col('.')) + call assert_beeps("normal 19|%") + call assert_beeps("normal 22|%") + set cpo+=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(19, col('.')) + normal 27|% + call assert_equal(22, col('.')) + normal 19|% + call assert_equal(15, col('.')) + normal 22|% + call assert_equal(27, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for cursor movement with '-' in 'cpoptions' +func Test_cpo_minus() + throw 'Skipped: Nvim does not support cpoptions flag "-"' + new + call setline(1, ['foo', 'bar', 'baz']) + let save_cpo = &cpo + set cpo+=- + call assert_beeps('normal 10j') + call assert_equal(1, line('.')) + normal G + call assert_beeps('normal 10k') + call assert_equal(3, line('.')) + call assert_fails(10, 'E16:') + close! + let &cpo = save_cpo +endfunc + +" Test for the '+' flag in 'cpo' ('write file' command resets the 'modified' +" flag) +func Test_cpo_plus() + let save_cpo = &cpo + call writefile([], 'Xfile') + new Xfile + call setline(1, 'foo') + write X1 + call assert_equal(1, &modified) + set cpo+=+ + write X2 + call assert_equal(0, &modified) + close! + call delete('Xfile') + call delete('X1') + call delete('X2') + let &cpo = save_cpo +endfunc + +" Test for the '*' flag in 'cpo' (':*' is same as ':@') +func Test_cpo_star() + throw 'Skipped: Nvim does not support cpoptions flag "*"' + let save_cpo = &cpo + let x = 0 + new + set cpo-=* + let @a = 'let x += 1' + call assert_fails('*a', 'E20:') + set cpo+=* + *a + call assert_equal(1, x) + close! + let &cpo = save_cpo +endfunc + +" Test for the '<' flag in 'cpo' is in the test_mapping.vim file + +" Test for the '>' flag in 'cpo' (use a new line when appending to a register) +func Test_cpo_gt() + let save_cpo = &cpo + new + call setline(1, 'one two') + set cpo-=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("oneone", @r) + set cpo+=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("\none\none", @r) + close! + let &cpo = save_cpo +endfunc + +" Test for the ';' flag in 'cpo' +" Test for t,f,F,T movement commands and 'cpo-;' setting +func Test_cpo_semicolon() + let save_cpo = &cpo + new + call append(0, ["aaa two three four", " zzz", "yyy ", + \ "bbb yee yoo four", "ccc two three four", + \ "ddd yee yoo four"]) + set cpo-=; + 1 + normal! 0tt;D + 2 + normal! 0fz;D + 3 + normal! $Fy;D + 4 + normal! $Ty;D + set cpo+=; + 5 + normal! 0tt;;D + 6 + normal! $Ty;;D + + call assert_equal('aaa two', getline(1)) + call assert_equal(' z', getline(2)) + call assert_equal('y', getline(3)) + call assert_equal('bbb y', getline(4)) + call assert_equal('ccc', getline(5)) + call assert_equal('ddd yee y', getline(6)) + close! + let &cpo = save_cpo +endfunc + +" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators) +func Test_cpo_hash() + throw 'Skipped: Nvim does not support cpoptions flag "#"' + let save_cpo = &cpo + new + set cpo-=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['three'], getline(1, '$')) + normal gg2ofour + call assert_equal(['three', 'four', 'four'], getline(1, '$')) + normal gg2Otwo + call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$')) + %d + set cpo+=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['', 'two', 'three'], getline(1, '$')) + normal gg2oone + call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) + normal gg2Ozero + call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '&' flag in 'cpo'. The swap file is kept when a buffer is still +" loaded and ':preserve' is used. +func Test_cpo_ampersand() + throw 'Skipped: Nvim does not support cpoptions flag "&"' + call writefile(['one'], 'Xfile') + let after =<< trim [CODE] + set cpo+=& + preserve + qall + [CODE] + if RunVim([], after, 'Xfile') + call assert_equal(1, filereadable('.Xfile.swp')) + call delete('.Xfile.swp') + endif + call delete('Xfile') +endfunc + +" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern) +func Test_cpo_backslash() + throw 'Skipped: Nvim does not support cpoptions flag "\"' + let save_cpo = &cpo + new + call setline(1, ['', " \\-string"]) + set cpo-=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(3, col('.')) + set cpo+=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(2, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '/' flag in 'cpo' is in the test_substitute.vim file + +" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a { +" character at the start of a line) +func Test_cpo_brace() + throw 'Skipped: Nvim does not support cpoptions flag "{"' + let save_cpo = &cpo + new + call setline(1, ['', '{', ' int i;', '}', '']) + set cpo-={ + normal gg} + call assert_equal(5, line('.')) + normal G{ + call assert_equal(1, line('.')) + set cpo+={ + normal gg} + call assert_equal(2, line('.')) + normal G{ + call assert_equal(2, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '.' flag in 'cpo' (:cd command fails if the current buffer is +" modified) +func Test_cpo_dot() + throw 'Skipped: Nvim does not support cpoptions flag "."' + let save_cpo = &cpo + new Xfoo + call setline(1, 'foo') + let save_dir = getcwd() + set cpo+=. + + " :cd should fail when buffer is modified and 'cpo' contains dot. + call assert_fails('cd ..', 'E747:') + call assert_equal(save_dir, getcwd()) + + " :cd with exclamation mark should succeed. + cd! .. + call assert_notequal(save_dir, getcwd()) + + " :cd should succeed when buffer has been written. + w! + exe 'cd ' .. fnameescape(save_dir) + call assert_equal(save_dir, getcwd()) + + call delete('Xfoo') + set cpo& + close! + let &cpo = save_cpo +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1cb71664bd..831efdbfc2 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1198,6 +1198,33 @@ func Test_diff_maintains_change_mark() delfunc DiffMaintainsChangeMark endfunc +" Test for 'patchexpr' +func Test_patchexpr() + let g:patch_args = [] + func TPatch() + call add(g:patch_args, readfile(v:fname_in)) + call add(g:patch_args, readfile(v:fname_diff)) + call writefile(['output file'], v:fname_out) + endfunc + set patchexpr=TPatch() + + call writefile(['input file'], 'Xinput') + call writefile(['diff file'], 'Xdiff') + %bwipe! + edit Xinput + diffpatch Xdiff + call assert_equal('output file', getline(1)) + call assert_equal('Xinput.new', bufname()) + call assert_equal(2, winnr('$')) + call assert_true(&diff) + + call delete('Xinput') + call delete('Xdiff') + set patchexpr& + delfunc TPatch + %bwipe! +endfunc + func Test_diff_rnu() CheckScreendump diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 13796449ab..679fe89628 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -195,8 +195,6 @@ func Test_edit_long_file_name() call VerifyScreenDump(buf, 'Test_long_file_name_1', {}) - call term_sendkeys(buf, ":q\<cr>") - " clean up call StopVimInTerminal(buf) call delete(longName) diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 9783ed19a7..89fd73351d 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1433,9 +1433,7 @@ endfunc func Test_edit_rightleft() " Cursor in rightleft mode moves differently - if !exists("+rightleft") - return - endif + CheckFeature rightleft call NewWindow(10, 20) call setline(1, ['abc', 'def', 'ghi']) call cursor(1, 2) @@ -1480,6 +1478,13 @@ func Test_edit_rightleft() \" ihg", \" ~"] call assert_equal(join(expect, "\n"), join(lines, "\n")) + %d _ + " call test_override('redraw_flag', 1) + " call test_override('char_avail', 1) + call feedkeys("a\<C-V>x41", "xt") + redraw! + call assert_equal(repeat(' ', 19) .. 'A', Screenline(1)) + " call test_override('ALL', 0) set norightleft bw! endfunc @@ -1724,40 +1729,6 @@ func Test_edit_illegal_filename() close! endfunc -" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') -func Test_edit_cpo_H() - throw 'Skipped: Nvim does not support cpoptions flag "H"' - new - call setline(1, ' ') - normal! Ia - call assert_equal(' a', getline(1)) - set cpo+=H - call setline(1, ' ') - normal! Ia - call assert_equal(' a ', getline(1)) - set cpo-=H - close! -endfunc - -" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') -func Test_edit_cpo_L() - new - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo+=L - set list - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) - set nolist - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo-=L - %bw! -endfunc - " Test for editing a directory func Test_edit_is_a_directory() CheckEnglish @@ -1902,6 +1873,107 @@ func Test_edit_insertmode_ex_edit() call delete('Xtest_edit_insertmode_ex_edit') endfunc +" Pressing escape in 'insertmode' should beep +func Test_edit_insertmode_esc_beeps() + throw "Skipped: Nvim does not support 'insertmode'" + new + set insertmode + call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')") + set insertmode& + " unsupported CTRL-G command should beep in insert mode. + call assert_beeps("normal i\<C-G>l") + close! +endfunc + +" Test for 'hkmap' and 'hkmapp' +func Test_edit_hkmap() + CheckFeature rightleft + if has('win32') && !has('gui') + " Test fails on the MS-Windows terminal version + return + endif + new + + set revins hkmap + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let str ..= '`/'',.;' + call feedkeys('i' .. str, 'xt') + let expected = "óõú,.;" + let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA" + let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù" + call assert_equal(expected, getline(1)) + + %d + set revins hkmap hkmapp + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + call feedkeys('i' .. str, 'xt') + let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA" + let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà" + call assert_equal(expected, getline(1)) + + set revins& hkmap& hkmapp& + close! +endfunc + +" Test for 'allowrevins' and using CTRL-_ in insert mode +func Test_edit_allowrevins() + CheckFeature rightleft + new + set allowrevins + call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt') + call assert_equal('ABCFEDGHI', getline(1)) + set allowrevins& + close! +endfunc + +" Test for inserting a register in insert mode using CTRL-R +func Test_edit_insert_reg() + throw 'Skipped: use test/functional/legacy/edit_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return 'r' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + call test_override('char_avail', 1) + let @r = 'sample' + call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") + call assert_equal('"', g:Line) + call test_override('ALL', 0) + close! +endfunc + +" When a character is inserted at the last position of the last line in a +" window, the window contents should be scrolled one line up. If the top line +" is part of a fold, then the entire fold should be scrolled up. +func Test_edit_lastline_scroll() + new + let h = winheight(0) + let lines = ['one', 'two', 'three'] + let lines += repeat(['vim'], h - 4) + call setline(1, lines) + call setline(h, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(2, line('w0')) + + " scroll with a fold + 1,2fold + normal gg + call setline(h + 1, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(3, line('w0')) + + close! +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index dc110af356..46482c34a1 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -20,13 +20,8 @@ func Test_nocatch_restore_silent_emsg() throw 1 catch endtry - echoerr 'wrong' - let c1 = nr2char(screenchar(&lines, 1)) - let c2 = nr2char(screenchar(&lines, 2)) - let c3 = nr2char(screenchar(&lines, 3)) - let c4 = nr2char(screenchar(&lines, 4)) - let c5 = nr2char(screenchar(&lines, 5)) - call assert_equal('wrong', c1 . c2 . c3 . c4 . c5) + echoerr 'wrong again' + call assert_equal('wrong again', ScreenLine(&lines)) endfunc func Test_mkdir_p() @@ -120,6 +115,13 @@ func Test_readfile_binary() call delete('XReadfile_bin') endfunc +func Test_readfile_binary_empty() + call writefile([], 'Xempty-file') + " This used to compare uninitialized memory in Vim <= 8.2.4065 + call assert_equal([''], readfile('Xempty-file', 'b')) + call delete('Xempty-file') +endfunc + func Test_readfile_bom() call writefile(["\ufeffFOO", "FOO\ufeffBAR"], 'XReadfile_bom') call assert_equal(['FOO', 'FOOBAR'], readfile('XReadfile_bom')) @@ -360,6 +362,11 @@ func Test_curly_assignment() unlet g:gvar endfunc +func Test_deep_recursion() + " this was running out of stack + call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((') +endfunc + " K_SPECIAL in the modified character used be escaped, which causes " double-escaping with feedkeys() or as the return value of an <expr> mapping, " and doesn't match what getchar() returns, diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 2f734cba26..93100732ed 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -97,7 +97,6 @@ func Test_Ex_substitute() call term_sendkeys(buf, ":vi\<CR>") call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) - call term_sendkeys(buf, ":q!\n") call StopVimInTerminal(buf) endfunc diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 7692d4fc55..582dcaac2c 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -479,12 +479,21 @@ endfunc func Test_redir_cmd() call assert_fails('redir @@', 'E475:') call assert_fails('redir abc', 'E475:') + call assert_fails('redir => 1abc', 'E474:') + call assert_fails('redir => a b', 'E488:') + call assert_fails('redir => abc[1]', 'E121:') + let b = 0zFF + call assert_fails('redir =>> b', 'E734:') + unlet b + if has('unix') + " Redirecting to a directory name call mkdir('Xdir') call assert_fails('redir > Xdir', 'E17:') call delete('Xdir', 'd') endif if !has('bsd') + " Redirecting to a read-only file call writefile([], 'Xfile') call setfperm('Xfile', 'r--r--r--') call assert_fails('redir! > Xfile', 'E190:') @@ -674,6 +683,12 @@ func Test_sandbox() sandbox call Sandbox_tests() endfunc +func Test_command_not_implemented_E319() + if !has('mzscheme') + call assert_fails('mzscheme', 'E319:') + endif +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim index 471c77853d..62c66192ef 100644 --- a/src/nvim/testdir/test_exists.vim +++ b/src/nvim/testdir/test_exists.vim @@ -68,6 +68,10 @@ func Test_exists() " Existing environment variable let $EDITOR_NAME = 'Vim Editor' call assert_equal(1, exists('$EDITOR_NAME')) + if has('unix') + " ${name} environment variables are supported only on Unix-like systems + call assert_equal(1, exists('${VIM}')) + endif " Non-existing environment variable call assert_equal(0, exists('$NON_ENV_VAR')) @@ -323,3 +327,5 @@ endfunc func Test_exists_funcarg() call FuncArg_Tests("arg1", "arg2") endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index aa131a49ff..4f5bb67d21 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -90,14 +90,26 @@ func Test_expandcmd() " Test for expression expansion `= let $FOO= "blue" call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`")) + let x = expandcmd("`=axbycz`") + call assert_equal('`=axbycz`', x) + call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:') + let x = expandcmd("`=axbycz`", #{abc: []}) + call assert_equal('`=axbycz`', x) " Test for env variable with spaces let $FOO= "foo bar baz" call assert_equal("e foo bar baz", expandcmd("e $FOO")) - if has('unix') - " test for using the shell to expand a command argument - call assert_equal('{1..4}', expandcmd('{1..4}')) + if has('unix') && executable('bash') + " test for using the shell to expand a command argument. + " only bash supports the {..} syntax + set shell=bash + let x = expandcmd('{1..4}') + call assert_equal('{1..4}', x) + call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:') + let x = expandcmd('{1..4}', #{error: v:true}) + call assert_equal('{1..4}', x) + set shell& endif unlet $FOO diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index 80bfdb8553..454d76f0aa 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -139,6 +139,7 @@ func Test_expand_wildignore() call assert_equal('test_expand_func.vim', expand('test_expand_func.vim', 1)) call assert_equal(['test_expand_func.vim'], \ expand('test_expand_func.vim', 1, 1)) + call assert_fails("call expand('*', [])", 'E745:') set wildignore& endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index c63a969e50..ea874cc398 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -46,6 +46,7 @@ func Test_dict() call assert_equal('zero', d[0]) call assert_true(has_key(d, '')) call assert_true(has_key(d, 'a')) + call assert_fails("let i = has_key([], 'a')", 'E715:') let d[''] = 'none' let d['a'] = 'aaa' @@ -64,6 +65,8 @@ func Test_strgetchar() call assert_equal(-1, strgetchar('axb', -1)) call assert_equal(-1, strgetchar('axb', 3)) call assert_equal(-1, strgetchar('', 0)) + call assert_fails("let c=strgetchar([], 1)", 'E730:') + call assert_fails("let c=strgetchar('axb', [])", 'E745:') endfunc func Test_strcharpart() @@ -98,13 +101,6 @@ func Test_loop_over_null_list() endfor endfunc -func Test_compare_null_dict() - call assert_fails('let x = v:_null_dict[10]') - call assert_equal({}, {}) - call assert_equal(v:_null_dict, v:_null_dict) - call assert_notequal({}, v:_null_dict) -endfunc - func Test_set_reg_null_list() call setreg('x', v:_null_list) endfunc @@ -491,49 +487,6 @@ function Test_max_min_errors() call assert_fails('call min(v:true)', 'min()') endfunc -func Test_substitute_expr() - let g:val = 'XXX' - call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) - call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ '\=nr2char("0x" . submatch(1))', 'g')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ {-> nr2char("0x" . submatch(1))}, 'g')) - - call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', - \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) - - func Recurse() - return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') - endfunc - " recursive call works - call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) -endfunc - -func Test_invalid_submatch() - " This was causing invalid memory access in Vim-7.4.2232 and older - call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') -endfunc - -func Test_substitute_expr_arg() - call assert_equal('123456789-123456789=', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456-123456=789', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456789-123456789x=', substitute('123456789', - \ '\(.\)\(.\)\(.*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') -endfunc - func Test_function_with_funcref() let s:f = function('type') let s:fref = function(s:f) @@ -575,6 +528,7 @@ func Test_setmatches() endif eval set->setmatches() call assert_equal(exp, getmatches()) + call assert_fails('let m = setmatches([], [])', 'E745:') endfunc func Test_empty_concatenate() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 3888a384c7..c6f90604e5 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -122,7 +122,7 @@ let s:filename_checks = { \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'], - \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'], + \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cook': ['file.cook'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index 1cd3a2287b..c75177ea39 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -86,6 +86,13 @@ func Test_map_filter_fails() call assert_fails('call filter([1], "42 +")', 'E15:') call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:') call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') + call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:') + call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:') + call assert_fails("let l = filter([1, 2], {})", 'E731:') + call assert_equal(v:_null_list, filter(v:_null_list, 0)) + call assert_equal(v:_null_dict, filter(v:_null_dict, 0)) + call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val')) + call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val')) endfunc func Test_map_and_modify() diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim index 32cb059e26..41d47d6a06 100644 --- a/src/nvim/testdir/test_fixeol.vim +++ b/src/nvim/testdir/test_fixeol.vim @@ -1,16 +1,17 @@ -" Tests for 'fixeol' and 'eol' +" Tests for 'fixeol', 'eof' and 'eol' + func Test_fixeol() " first write two test files – with and without trailing EOL " use Unix fileformat for consistency set ff=unix enew! - call setline('.', 'with eol') + call setline('.', 'with eol or eof') w! XXEol enew! - set noeol nofixeol - call setline('.', 'without eol') + set noeof noeol nofixeol + call setline('.', 'without eol or eof') w! XXNoEol - set eol fixeol + set eol eof fixeol bwipe XXEol XXNoEol " try editing files with 'fixeol' disabled @@ -33,16 +34,85 @@ func Test_fixeol() w >>XXTestEol w >>XXTestNoEol - call assert_equal(['with eol', 'END'], readfile('XXEol')) - call assert_equal(['without eolEND'], readfile('XXNoEol')) - call assert_equal(['with eol', 'stays eol', 'END'], readfile('XXTestEol')) - call assert_equal(['without eol', 'stays withoutEND'], + call assert_equal(['with eol or eof', 'END'], readfile('XXEol')) + call assert_equal(['without eol or eofEND'], readfile('XXNoEol')) + call assert_equal(['with eol or eof', 'stays eol', 'END'], readfile('XXTestEol')) + call assert_equal(['without eol or eof', 'stays withoutEND'], \ readfile('XXTestNoEol')) call delete('XXEol') call delete('XXNoEol') call delete('XXTestEol') call delete('XXTestNoEol') - set ff& fixeol& eol& + set ff& fixeol& eof& eol& + enew! +endfunc + +func Test_eof() + let data = 0z68656c6c6f.0d0a.776f726c64 " "hello\r\nworld" + + " 1. Eol, Eof + " read + call writefile(data + 0z0d0a.1a, 'XXEolEof') + e! XXEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a.1a, readblob('XXEolEof')) + + " 2. NoEol, Eof + " read + call writefile(data + 0z1a, 'XXNoEolEof') + e! XXNoEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolEof')) + set nofixeol + w! + call assert_equal(data + 0z1a, readblob('XXNoEolEof')) + + " 3. Eol, NoEof + " read + call writefile(data + 0z0d0a, 'XXEolNoEof') + e! XXEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + + " 4. NoEol, NoEof + " read + call writefile(data, 'XXNoEolNoEof') + e! XXNoEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolNoEof')) + set nofixeol + w! + call assert_equal(data, readblob('XXNoEolNoEof')) + + call delete('XXEolEof') + call delete('XXNoEolEof') + call delete('XXEolNoEof') + call delete('XXNoEolNoEof') + set ff& fixeol& eof& eol& enew! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 327f0f73f2..0a9be310ff 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -1,5 +1,6 @@ " Test for folding +source check.vim source view_util.vim source screendump.vim @@ -727,9 +728,7 @@ func Test_fold_last_line_with_pagedown() endfunc func Test_folds_with_rnu() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set fdm=marker rnu foldcolumn=2', diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 7ad0cb5884..d2603809b9 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -19,6 +19,27 @@ func Test_00_bufexists() call assert_equal(0, bufexists('Xfoo')) endfunc +func Test_has() + throw 'Skipped: Nvim has removed some features' + call assert_equal(1, has('eval')) + call assert_equal(1, has('eval', 1)) + + if has('unix') + call assert_equal(1, or(has('ttyin'), 1)) + call assert_equal(0, and(has('ttyout'), 0)) + call assert_equal(1, has('multi_byte_encoding')) + endif + call assert_equal(1, has('vcon', 1)) + call assert_equal(1, has('mouse_gpm_enabled', 1)) + + call assert_equal(0, has('nonexistent')) + call assert_equal(0, has('nonexistent', 1)) + + " Will we ever have patch 9999? + let ver = 'patch-' .. v:version / 100 .. '.' .. v:version % 100 .. '.9999' + call assert_equal(0, has(ver)) +endfunc + func Test_empty() call assert_equal(1, empty('')) call assert_equal(0, empty('a')) @@ -67,9 +88,11 @@ func Test_len() call assert_equal(2, len('ab')) call assert_equal(0, len([])) + call assert_equal(0, len(v:_null_list)) call assert_equal(2, len([2, 1])) call assert_equal(0, len({})) + call assert_equal(0, len(v:_null_dict)) call assert_equal(2, len({'a': 1, 'b': 2})) " call assert_fails('call len(v:none)', 'E701:') @@ -188,7 +211,7 @@ func Test_str2nr() if has('float') call assert_fails('call str2nr(1.2)', 'E806:') endif - call assert_fails('call str2nr(10, [])', 'E474:') + call assert_fails('call str2nr(10, [])', 'E745:') endfunc func Test_strftime() @@ -381,6 +404,8 @@ func Test_strpart() call assert_equal('abcdefg', 'abcdefg'->strpart(-2)) call assert_equal('fg', strpart('abcdefg', 5, 4)) call assert_equal('defg', strpart('abcdefg', 3)) + call assert_equal('', strpart('abcdefg', 10)) + call assert_fails("let s=strpart('abcdef', [])", 'E745:') call assert_equal('lép', strpart('éléphant', 2, 4)) call assert_equal('léphant', strpart('éléphant', 2)) @@ -550,6 +575,15 @@ endfunc func Test_tr() call assert_equal('foo', tr('bar', 'bar', 'foo')) call assert_equal('zxy', 'cab'->tr('abc', 'xyz')) + call assert_fails("let s=tr([], 'abc', 'def')", 'E730:') + call assert_fails("let s=tr('abc', [], 'def')", 'E730:') + call assert_fails("let s=tr('abc', 'abc', [])", 'E730:') + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + " set encoding=latin1 + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + call assert_equal('hEllO', tr('hello', 'eo', 'EO')) + call assert_equal('hello', tr('hello', 'xy', 'ab')) + set encoding=utf8 endfunc " Tests for the mode() function @@ -764,13 +798,39 @@ func Test_mode() delfunction OperatorFunc endfunc +" Test for append() func Test_append() enew! split call append(0, ["foo"]) + call append(1, []) + call append(1, v:_null_list) + call assert_equal(['foo', ''], getline(1, '$')) split only undo + undo + + " Using $ instead of '$' must give an error + call assert_fails("call append($, 'foobar')", 'E116:') +endfunc + +" Test for setline() +func Test_setline() + new + call setline(0, ["foo"]) + call setline(0, []) + call setline(0, v:_null_list) + call setline(1, ["bar"]) + call setline(1, []) + call setline(1, v:_null_list) + call setline(2, []) + call setline(2, v:_null_list) + call setline(3, []) + call setline(3, v:_null_list) + call setline(2, ["baz"]) + call assert_equal(['bar', 'baz'], getline(1, '$')) + close! endfunc func Test_getbufvar() @@ -804,6 +864,10 @@ func Test_getbufvar() call assert_equal(0, getbufvar(bnr, '&autoindent')) call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + " Set and get a buffer-local variable + call setbufvar(bnr, 'bufvar_test', ['one', 'two']) + call assert_equal(['one', 'two'], getbufvar(bnr, 'bufvar_test')) + " Open new window with forced option values set fileformats=unix,dos new ++ff=dos ++bin ++enc=iso-8859-2 @@ -842,6 +906,8 @@ func Test_stridx() call assert_equal(-1, stridx('hello', 'l', 10)) call assert_equal(2, stridx('hello', 'll')) call assert_equal(-1, stridx('hello', 'hello world')) + call assert_fails("let n=stridx('hello', [])", 'E730:') + call assert_fails("let n=stridx([], 'l')", 'E730:') endfunc func Test_strridx() @@ -858,6 +924,8 @@ func Test_strridx() call assert_equal(-1, strridx('hello', 'l', -1)) call assert_equal(2, strridx('hello', 'll')) call assert_equal(-1, strridx('hello', 'hello world')) + call assert_fails("let n=strridx('hello', [])", 'E730:') + call assert_fails("let n=strridx([], 'l')", 'E730:') endfunc func Test_match_func() @@ -867,6 +935,12 @@ func Test_match_func() call assert_equal(-1, match('testing', 'ing', 8)) call assert_equal(1, match(['vim', 'testing', 'execute'], 'ing')) call assert_equal(-1, match(['vim', 'testing', 'execute'], 'img')) + call assert_fails("let x=match('vim', [])", 'E730:') + call assert_equal(3, match(['a', 'b', 'c', 'a'], 'a', 1)) + call assert_equal(-1, match(['a', 'b', 'c', 'a'], 'a', 5)) + call assert_equal(4, match('testing', 'ing', -1)) + call assert_fails("let x=match('testing', 'ing', 0, [])", 'E745:') + call assert_equal(-1, match(v:_null_list, 2)) endfunc func Test_matchend() @@ -973,6 +1047,7 @@ func Test_byte2line_line2byte() bw! endfunc +" Test for byteidx() and byteidxcomp() functions func Test_byteidx() let a = '.é.' " one char of two bytes call assert_equal(0, byteidx(a, 0)) @@ -992,6 +1067,7 @@ func Test_byteidx() call assert_equal(4, b->byteidx(2)) call assert_equal(5, b->byteidx(3)) call assert_equal(-1, b->byteidx(4)) + call assert_fails("call byteidx([], 0)", 'E730:') call assert_equal(0, b->byteidxcomp(0)) call assert_equal(1, b->byteidxcomp(1)) @@ -999,6 +1075,7 @@ func Test_byteidx() call assert_equal(4, b->byteidxcomp(3)) call assert_equal(5, b->byteidxcomp(4)) call assert_equal(-1, b->byteidxcomp(5)) + call assert_fails("call byteidxcomp([], 0)", 'E730:') endfunc " Test for charidx() @@ -1009,7 +1086,9 @@ func Test_charidx() 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(a, -1)) call assert_equal(-1, charidx('', 0)) + call assert_equal(-1, charidx(v:_null_string, 0)) " count composing characters call assert_equal(0, charidx(a, 0, 1)) @@ -1022,8 +1101,8 @@ func Test_charidx() 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:') + call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:') + call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:') endfunc func Test_count() @@ -1195,6 +1274,7 @@ func Test_col() norm gg4|mx6|mY2| call assert_equal(2, col('.')) call assert_equal(7, col('$')) + call assert_equal(2, col('v')) call assert_equal(4, col("'x")) call assert_equal(6, col("'Y")) call assert_equal(2, [1, 2]->col()) @@ -1205,6 +1285,35 @@ func Test_col() call assert_equal(0, col([2, '$'])) call assert_equal(0, col([1, 100])) call assert_equal(0, col([1])) + + " test for getting the visual start column + func T() + let g:Vcol = col('v') + return '' + endfunc + let g:Vcol = 0 + xmap <expr> <F2> T() + exe "normal gg3|ve\<F2>" + call assert_equal(3, g:Vcol) + xunmap <F2> + delfunc T + + " Test for the visual line start and end marks '< and '> + call setline(1, ['one', 'one two', 'one two three']) + "normal! ggVG + call feedkeys("ggVG\<Esc>", 'xt') + call assert_equal(1, col("'<")) + call assert_equal(14, col("'>")) + " Delete the last line of the visually selected region + $d + call assert_notequal(14, col("'>")) + + " Test with 'virtualedit' + set virtualedit=all + call cursor(1, 10) + call assert_equal(4, col('.')) + set virtualedit& + bw! endfunc @@ -1248,12 +1357,15 @@ endfunc " Test for the inputdialog() function func Test_inputdialog() - CheckNotGui - - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') - call assert_equal('xx', v) - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') - call assert_equal('yy', v) + if has('gui_running') + call assert_fails('let v=inputdialog([], "xx")', 'E730:') + call assert_fails('let v=inputdialog("Q", [])', 'E730:') + else + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') + call assert_equal('xx', v) + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') + call assert_equal('yy', v) + endif endfunc " Test for inputlist() @@ -1302,6 +1414,10 @@ func Test_balloon_show() " This won't do anything but must not crash either. call balloon_show('hi!') + if !has('gui_running') + call balloon_show(range(3)) + call balloon_show([]) + endif endfunc func Test_shellescape() @@ -1612,11 +1728,11 @@ func Test_libcall_libcallnr() call assert_equal(4, 'abcd'->libcallnr(libc, 'strlen')) call assert_equal(char2nr('A'), char2nr('a')->libcallnr(libc, 'toupper')) - call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') - call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) - call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') - call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", ['', 'E364:']) + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", ['', 'E364:']) endfunc sandbox function Fsandbox() @@ -1633,6 +1749,10 @@ func Test_func_sandbox() call assert_fails('call Fsandbox()', 'E48:') delfunc Fsandbox + + " From a sandbox try to set a predefined variable (which cannot be modified + " from a sandbox) + call assert_fails('sandbox let v:lnum = 10', 'E794:') endfunc func EditAnotherFile() @@ -1715,9 +1835,8 @@ endfunc func Test_confirm() " requires a UI to be active throw 'Skipped: use test/functional/vimscript/input_spec.lua' - if !has('unix') || has('gui_running') - return - endif + CheckUnix + CheckNotGui call feedkeys('o', 'L') let a = confirm('Press O to proceed') @@ -1732,7 +1851,7 @@ func Test_confirm() call assert_equal(2, a) " confirm() should return 0 when pressing CTRL-C. - call feedkeys("\<C-c>", 'L') + call feedkeys("\<C-C>", 'L') let a = confirm('Are you sure?', "&Yes\n&No") call assert_equal(0, a) @@ -1827,6 +1946,7 @@ func Test_call() call assert_equal(3, 'len'->call([123])) call assert_fails("call call('len', 123)", 'E714:') call assert_equal(0, call('', [])) + call assert_equal(0, call('len', v:_null_list)) function Mylen() dict return len(self.data) @@ -1834,11 +1954,15 @@ func Test_call() let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} eval mydict.len->call([], mydict)->assert_equal(4) call assert_fails("call call('Mylen', [], 0)", 'E715:') + call assert_fails('call foo', 'E107:') endfunc func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) call assert_equal(120, 'x'->char2nr()) + " set encoding=latin1 + call assert_equal(120, 'x'->char2nr()) + set encoding=utf-8 endfunc func Test_charclass() @@ -1915,6 +2039,280 @@ func Test_bufadd_bufload() call delete('XotherName') endfunc +func Test_range() + " destructuring + let [x, y] = range(2) + call assert_equal([0, 1], [x, y]) + + " index + call assert_equal(4, range(1, 10)[3]) + + " add() + call assert_equal([0, 1, 2, 3], add(range(3), 3)) + call assert_equal([0, 1, 2, [0, 1, 2]], add([0, 1, 2], range(3))) + call assert_equal([0, 1, 2, [0, 1, 2]], add(range(3), range(3))) + + " append() + new + call append('.', range(5)) + call assert_equal(['', '0', '1', '2', '3', '4'], getline(1, '$')) + bwipe! + + " appendbufline() + new + call appendbufline(bufnr(''), '.', range(5)) + call assert_equal(['0', '1', '2', '3', '4', ''], getline(1, '$')) + bwipe! + + " call() + func TwoArgs(a, b) + return [a:a, a:b] + endfunc + call assert_equal([0, 1], call('TwoArgs', range(2))) + + " col() + new + call setline(1, ['foo', 'bar']) + call assert_equal(2, col(range(1, 2))) + bwipe! + + " complete() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>" + " complete_info() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>\<C-r>=[complete_info(range(5)), ''][1]\<CR>" + + " copy() + call assert_equal([1, 2, 3], copy(range(1, 3))) + + " count() + call assert_equal(0, count(range(0), 3)) + call assert_equal(0, count(range(2), 3)) + call assert_equal(1, count(range(5), 3)) + + " cursor() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call cursor(range(1, 2)) + call assert_equal([2, 1], [col('.'), line('.')]) + bwipe! + + " deepcopy() + call assert_equal([1, 2, 3], deepcopy(range(1, 3))) + + " empty() + call assert_true(empty(range(0))) + call assert_false(empty(range(2))) + + " execute() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call execute(range(3)) + call assert_equal(2, line('.')) + bwipe! + + " extend() + call assert_equal([1, 2, 3, 4], extend([1], range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), [2, 3, 4])) + + " filter() + call assert_equal([1, 3], filter(range(5), 'v:val % 2')) + + " funcref() + call assert_equal([0, 1], funcref('TwoArgs', range(2))()) + + " function() + call assert_equal([0, 1], function('TwoArgs', range(2))()) + + " garbagecollect() + let thelist = [1, range(2), 3] + let otherlist = range(3) + call test_garbagecollect_now() + + " get() + call assert_equal(4, get(range(1, 10), 3)) + call assert_equal(-1, get(range(1, 10), 42, -1)) + + " index() + call assert_equal(1, index(range(1, 5), 2)) + call assert_fails("echo index([1, 2], 1, [])", 'E745:') + + " inputlist() + call feedkeys(":let result = inputlist(range(10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + call feedkeys(":let result = inputlist(range(3, 10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + + " insert() + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42)) + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42, 0)) + call assert_equal([1, 42, 2, 3, 4, 5], insert(range(1, 5), 42, 1)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, 4)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, -1)) + call assert_equal([1, 2, 3, 4, 5, 42], insert(range(1, 5), 42, 5)) + + " join() + call assert_equal('0 1 2 3 4', join(range(5))) + + " json_encode() + " call assert_equal('[0,1,2,3]', json_encode(range(4))) + call assert_equal('[0, 1, 2, 3]', json_encode(range(4))) + + " len() + call assert_equal(0, len(range(0))) + call assert_equal(2, len(range(2))) + call assert_equal(5, len(range(0, 12, 3))) + call assert_equal(4, len(range(3, 0, -1))) + + " list2str() + call assert_equal('ABC', list2str(range(65, 67))) + call assert_fails('let s = list2str(5)', 'E474:') + + " lock() + let thelist = range(5) + lockvar thelist + + " map() + call assert_equal([0, 2, 4, 6, 8], map(range(5), 'v:val * 2')) + + " match() + call assert_equal(3, match(range(5), 3)) + + " matchaddpos() + highlight MyGreenGroup ctermbg=green guibg=green + call matchaddpos('MyGreenGroup', range(line('.'), line('.'))) + + " matchend() + call assert_equal(4, matchend(range(5), '4')) + call assert_equal(3, matchend(range(1, 5), '4')) + call assert_equal(-1, matchend(range(1, 5), '42')) + + " matchstrpos() + call assert_equal(['4', 4, 0, 1], matchstrpos(range(5), '4')) + call assert_equal(['4', 3, 0, 1], matchstrpos(range(1, 5), '4')) + call assert_equal(['', -1, -1, -1], matchstrpos(range(1, 5), '42')) + + " max() reverse() + call assert_equal(0, max(range(0))) + call assert_equal(0, max(range(10, 9))) + call assert_equal(9, max(range(10))) + call assert_equal(18, max(range(0, 20, 3))) + call assert_equal(20, max(range(20, 0, -3))) + call assert_equal(99999, max(range(100000))) + call assert_equal(99999, max(range(99999, 0, -1))) + call assert_equal(99999, max(reverse(range(100000)))) + call assert_equal(99999, max(reverse(range(99999, 0, -1)))) + + " min() reverse() + call assert_equal(0, min(range(0))) + call assert_equal(0, min(range(10, 9))) + call assert_equal(5, min(range(5, 10))) + call assert_equal(5, min(range(5, 10, 3))) + call assert_equal(2, min(range(20, 0, -3))) + call assert_equal(0, min(range(100000))) + call assert_equal(0, min(range(99999, 0, -1))) + call assert_equal(0, min(reverse(range(100000)))) + call assert_equal(0, min(reverse(range(99999, 0, -1)))) + + " remove() + call assert_equal(1, remove(range(1, 10), 0)) + call assert_equal(2, remove(range(1, 10), 1)) + call assert_equal(9, remove(range(1, 10), 8)) + call assert_equal(10, remove(range(1, 10), 9)) + call assert_equal(10, remove(range(1, 10), -1)) + call assert_equal([3, 4, 5], remove(range(1, 10), 2, 4)) + + " repeat() + call assert_equal([0, 1, 2, 0, 1, 2], repeat(range(3), 2)) + call assert_equal([0, 1, 2], repeat(range(3), 1)) + call assert_equal([], repeat(range(3), 0)) + call assert_equal([], repeat(range(5, 4), 2)) + call assert_equal([], repeat(range(5, 4), 0)) + + " reverse() + call assert_equal([2, 1, 0], reverse(range(3))) + call assert_equal([0, 1, 2, 3], reverse(range(3, 0, -1))) + call assert_equal([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], reverse(range(10))) + call assert_equal([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10], reverse(range(10, 20))) + call assert_equal([16, 13, 10], reverse(range(10, 18, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 19, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 20, 3))) + call assert_equal([11, 14, 17, 20], reverse(range(20, 10, -3))) + call assert_equal([], reverse(range(0))) + + " TODO: setpos() + " new + " call setline(1, repeat([''], bufnr(''))) + " call setline(bufnr('') + 1, repeat('x', bufnr('') * 2 + 6)) + " call setpos('x', range(bufnr(''), bufnr('') + 3)) + " bwipe! + + " setreg() + call setreg('a', range(3)) + call assert_equal("0\n1\n2\n", getreg('a')) + + " settagstack() + call settagstack(1, #{items : range(4)}) + + " sign_define() + call assert_fails("call sign_define(range(5))", "E715:") + call assert_fails("call sign_placelist(range(5))", "E715:") + + " sign_undefine() + " call assert_fails("call sign_undefine(range(5))", "E908:") + call assert_fails("call sign_undefine(range(5))", "E155:") + + " sign_unplacelist() + call assert_fails("call sign_unplacelist(range(5))", "E715:") + + " sort() + call assert_equal([0, 1, 2, 3, 4, 5], sort(range(5, 0, -1))) + + " string() + call assert_equal('[0, 1, 2, 3, 4]', string(range(5))) + + " taglist() with 'tagfunc' + func TagFunc(pattern, flags, info) + return range(10) + endfunc + set tagfunc=TagFunc + call assert_fails("call taglist('asdf')", 'E987:') + set tagfunc= + + " term_start() + if has('terminal') && has('termguicolors') + call assert_fails('call term_start(range(3, 4))', 'E474:') + let g:terminal_ansi_colors = range(16) + if has('win32') + let cmd = "cmd /c dir" + else + let cmd = "ls" + endif + call assert_fails('call term_start("' .. cmd .. '", #{term_finish: "close"})', 'E475:') + unlet g:terminal_ansi_colors + endif + + " type() + call assert_equal(v:t_list, type(range(5))) + + " uniq() + call assert_equal([0, 1, 2, 3, 4], uniq(range(5))) + + " errors + call assert_fails('let x=range(2, 8, 0)', 'E726:') + call assert_fails('let x=range(3, 1)', 'E727:') + call assert_fails('let x=range(1, 3, -2)', 'E727:') + call assert_fails('let x=range([])', 'E745:') + call assert_fails('let x=range(1, [])', 'E745:') + call assert_fails('let x=range(1, 4, [])', 'E745:') +endfunc + +func Test_garbagecollect_now_fails() + let v:testing = 0 + call assert_fails('call test_garbagecollect_now()', 'E1142:') + let v:testing = 1 +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') @@ -1958,6 +2356,13 @@ func Test_nr2char() call assert_equal("\x80\xfc\b" .. nr2char(0x40000000), eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for screenattr(), screenchar() and screenchars() functions +func Test_screen_functions() + call assert_equal(-1, screenattr(-1, -1)) + call assert_equal(-1, screenchar(-1, -1)) + call assert_equal([], screenchars(-1, -1)) +endfunc + " Test for getcurpos() and setpos() func Test_getcurpos_setpos() new diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index cb6851250c..bbfe374f51 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -39,7 +39,7 @@ endfunc func Test_global_error() call assert_fails('g\\a', 'E10:') call assert_fails('g', 'E148:') - call assert_fails('g/\(/y', 'E476:') + call assert_fails('g/\(/y', 'E54:') endfunc " Test for printing lines using :g with different search patterns diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index e84c45c635..2be82f4e3c 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -536,9 +536,7 @@ func Test_termguicolors() endfunc func Test_cursorline_after_yank() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul rnu', @@ -578,9 +576,7 @@ func Test_put_before_cursorline() endfunc func Test_cursorline_with_visualmode() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul', diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index 2559654f25..3c2b88ef9f 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -476,6 +476,10 @@ func Test_visual_increment_20() exec "norm! \<C-A>" call assert_equal(["b"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) + " decrement a and A and increment z and Z + call setline(1, ['a', 'A', 'z', 'Z']) + exe "normal 1G\<C-X>2G\<C-X>3G\<C-A>4G\<C-A>" + call assert_equal(['a', 'A', 'z', 'Z'], getline(1, '$')) endfunc " 21) block-wise increment on part of hexadecimal @@ -566,12 +570,14 @@ endfunc " 1) <ctrl-a> " 0b11111111111111111111111111111111 func Test_visual_increment_26() - set nrformats+=alpha + set nrformats+=bin call setline(1, ["0b11111111111111111111111111111110"]) exec "norm! \<C-V>$\<C-A>" call assert_equal(["0b11111111111111111111111111111111"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) - set nrformats-=alpha + exec "norm! \<C-V>$\<C-X>" + call assert_equal(["0b11111111111111111111111111111110"], getline(1, '$')) + set nrformats-=bin endfunc " 27) increment with 'rightreft', if supported @@ -772,7 +778,6 @@ func Test_normal_increment_03() endfunc func Test_increment_empty_line() - new call setline(1, ['0', '0', '0', '0', '0', '0', '']) exe "normal Gvgg\<C-A>" call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7)) @@ -783,8 +788,13 @@ func Test_increment_empty_line() exe "normal! c\<C-A>l" exe "normal! c\<C-X>l" call assert_equal('one two', getline(1)) +endfunc - bwipe! +" Try incrementing/decrementing a non-digit/alpha character +func Test_increment_special_char() + call setline(1, '!') + call assert_beeps("normal \<C-A>") + call assert_beeps("normal \<C-X>") endfunc " Try incrementing/decrementing a number when nrformats contains unsigned @@ -867,4 +877,21 @@ func Test_normal_increment_with_virtualedit() set virtualedit& endfunc +" Test for incrementing a signed hexadecimal and octal number +func Test_normal_increment_signed_hexoct_nr() + new + " negative sign before a hex number should be ignored + call setline(1, ["-0x9"]) + exe "norm \<C-A>" + call assert_equal(["-0xa"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-0x9"], getline(1, '$')) + call setline(1, ["-007"]) + exe "norm \<C-A>" + call assert_equal(["-010"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-007"], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index c178c87d3e..ce15243993 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -63,6 +63,7 @@ function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:') call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') + echo assert_fails('echo 10->{a -> a + 2}', 'E107:') endfunc func Test_not_lambda() @@ -125,7 +126,7 @@ func Test_lambda_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) @@ -208,9 +209,9 @@ func Test_lambda_circular_reference() endfunc call s:Foo() - call garbagecollect() + call test_garbagecollect_now() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile - call garbagecollect() + call test_garbagecollect_now() endfunc func Test_lambda_combination() @@ -239,11 +240,16 @@ func Test_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) + + call assert_match("^\n function <SNR>\\d\\+_bar() closure" + \ .. "\n1 let x += 1" + \ .. "\n2 return x" + \ .. "\n endfunction$", execute('func s:bar')) endfunc func Test_closure_unlet() @@ -257,7 +263,7 @@ func Test_closure_unlet() endfunc call assert_false(has_key(s:foo(), 'x')) - call garbagecollect() + call test_garbagecollect_now() endfunc func LambdaFoo() @@ -294,7 +300,7 @@ func Test_named_function_closure() endfunc call Afoo() call assert_equal(14, s:Abar()) - call garbagecollect() + call test_garbagecollect_now() call assert_equal(14, s:Abar()) endfunc diff --git a/src/nvim/testdir/test_langmap.vim b/src/nvim/testdir/test_langmap.vim index 2284704603..aaed77e109 100644 --- a/src/nvim/testdir/test_langmap.vim +++ b/src/nvim/testdir/test_langmap.vim @@ -52,7 +52,7 @@ func Test_langmap() set langmap=RL let g:counter = 0 nnoremap L;L <Cmd>let g:counter += 1<CR> - nnoremap <C-L> <Cmd>throw 'This mapping shoud not be triggered'<CR> + nnoremap <C-L> <Cmd>throw 'This mapping should not be triggered'<CR> " 'langmap' is applied to keys without modifiers when matching a mapping call feedkeys('R;R', 'tx') diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 6cb736a38a..f05e06f774 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -25,9 +25,62 @@ func Test_let() let s = "\na #1\nb #2" call assert_equal(s, out) + " Test for displaying a string variable + let s = 'vim' + let out = execute('let s') + let s = "\ns vim" + call assert_equal(s, out) + + " Test for displaying a list variable + let l = [1, 2] + let out = execute('let l') + let s = "\nl [1, 2]" + call assert_equal(s, out) + + " Test for displaying a dict variable + let d = {'k' : 'v'} + let out = execute('let d') + let s = "\nd {'k': 'v'}" + call assert_equal(s, out) + + " Test for displaying a function reference variable + let F = function('min') + let out = execute('let F') + let s = "\nF *min()" + call assert_equal(s, out) + let x = 0 if 0 | let x = 1 | endif call assert_equal(0, x) + + " Display a list item using an out of range index + let l = [10] + call assert_fails('let l[1]', 'E684:') + + " List special variable dictionaries + let g:Test_Global_Var = 5 + call assert_match("\nTest_Global_Var #5", execute('let g:')) + unlet g:Test_Global_Var + + let b:Test_Buf_Var = 8 + call assert_match("\nb:Test_Buf_Var #8", execute('let b:')) + unlet b:Test_Buf_Var + + let w:Test_Win_Var = 'foo' + call assert_equal("\nw:Test_Win_Var foo", execute('let w:')) + unlet w:Test_Win_Var + + let t:Test_Tab_Var = 'bar' + call assert_equal("\nt:Test_Tab_Var bar", execute('let t:')) + unlet t:Test_Tab_Var + + let s:Test_Script_Var = [7] + call assert_match("\ns:Test_Script_Var \\[7]", execute('let s:')) + unlet s:Test_Script_Var + + let l:Test_Local_Var = {'k' : 5} + call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:')) + call assert_match("v:errors []", execute('let v:')) endfunc func s:set_arg1(a) abort @@ -201,16 +254,59 @@ func Test_let_option_error() let &fillchars = _w endfunc +" Errors with the :let statement func Test_let_errors() let s = 'abcd' call assert_fails('let s[1] = 5', 'E689:') let l = [1, 2, 3] call assert_fails('let l[:] = 5', 'E709:') + + call assert_fails('let x:lnum=5', ['E121:', 'E488:']) + call assert_fails('let v:=5', 'E461:') + call assert_fails('let [a]', 'E474:') + call assert_fails('let [a, b] = [', 'E697:') + call assert_fails('let [a, b] = [10, 20', 'E696:') + call assert_fails('let [a, b] = 10', 'E714:') + call assert_fails('let [a, , b] = [10, 20]', 'E475:') + call assert_fails('let [a, b&] = [10, 20]', 'E475:') + call assert_fails('let $ = 10', 'E475:') + call assert_fails('let $FOO[1] = "abc"', 'E18:') + call assert_fails('let &buftype[1] = "nofile"', 'E18:') + let s = "var" + let var = 1 + call assert_fails('let var += [1,2]', 'E734:') + call assert_fails('let {s}.1 = 2', 'E18:') + call assert_fails('let a[1] = 5', 'E121:') + let l = [[1,2]] + call assert_fails('let l[:][0] = [5]', 'E708:') + let d = {'k' : 4} + call assert_fails('let d.# = 5', 'E713:') + call assert_fails('let d.m += 5', 'E734:') + let l = [1, 2] + call assert_fails('let l[2] = 0', 'E684:') + call assert_fails('let l[0:1] = [1, 2, 3]', 'E710:') + call assert_fails('let l[-2:-3] = [3, 4]', 'E684:') + call assert_fails('let l[0:4] = [5, 6]', 'E711:') + call assert_fails('let g:["a;b"] = 10', 'E461:') + call assert_fails('let g:.min = function("max")', 'E704:') + + " This test works only when the language is English + if v:lang == "C" || v:lang =~ '^[Ee]n' + call assert_fails('let [a ; b;] = [10, 20]', + \ 'Double ; in list of variables') + endif endfunc func Test_let_heredoc_fails() call assert_fails('let v =<< marker', 'E991:') + try + exe "let v =<< TEXT | abc | TEXT" + call assert_report('No exception thrown') + catch /E488:/ + catch + call assert_report("Caught exception: " .. v:exception) + endtry let text =<< trim END func WrongSyntax() @@ -243,6 +339,10 @@ func Test_let_heredoc_fails() call writefile(text, 'XheredocBadMarker') call assert_fails('source XheredocBadMarker', 'E221:') call delete('XheredocBadMarker') + + call writefile(['let v =<< TEXT', 'abc'], 'XheredocMissingMarker') + call assert_fails('source XheredocMissingMarker', 'E990:') + call delete('XheredocMissingMarker') endfunc func Test_let_heredoc_trim_no_indent_marker() @@ -361,3 +461,5 @@ E END call assert_equal([' x', ' \y', ' z'], [a, b, c]) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 9cef6905a5..ecf95ba8c0 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -31,6 +31,12 @@ func Test_list_slice() call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2]) call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8]) call assert_equal([], l[8:-1]) + call assert_equal([], l[0:-10]) + " perform an operation on a list slice + let l = [1, 2, 3] + let l[:1] += [1, 2] + let l[2:] -= [1] + call assert_equal([2, 4, 2], l) endfunc " List identity @@ -104,6 +110,8 @@ func Test_list_range_assign() let l = [0] let l[:] = [1, 2] call assert_equal([1, 2], l) + let l[-4:-1] = [5, 6] + call assert_equal([5, 6], l) endfunc " Test removing items in list @@ -143,6 +151,20 @@ func Test_list_func_remove() call assert_fails("call remove(l, l)", 'E745:') endfunc +" List add() function +func Test_list_add() + let l = [] + call add(l, 1) + call add(l, [2, 3]) + call add(l, []) + call add(l, v:_null_list) + call add(l, {'k' : 3}) + call add(l, {}) + call add(l, v:_null_dict) + call assert_equal([1, [2, 3], [], [], {'k' : 3}, {}, {}], l) + " call assert_equal(1, add(v:_null_list, 4)) +endfunc + " Tests for Dictionary type func Test_dict() @@ -166,6 +188,19 @@ func Test_dict() call filter(d, 'v:key =~ ''[ac391]''') call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d) + " duplicate key + call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:') + " missing comma + call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:') + " missing curly brace + call assert_fails("let d = {'k' : 10,", 'E723:') + " invalid key + call assert_fails('let d = #{++ : 10}', 'E15:') + " wrong type for key + call assert_fails('let d={[] : 10}', 'E730:') + " undefined variable as value + call assert_fails("let d={'k' : i}", 'E121:') + " allow key starting with number at the start, not a curly expression call assert_equal({'1foo': 77}, #{1foo: 77}) @@ -266,7 +301,7 @@ func Test_script_local_dict_func() unlet g:dict endfunc -" Test removing items in la dictionary +" Test removing items in a dictionary func Test_dict_func_remove() let d = {1:'a', 2:'b', 3:'c'} call assert_equal('b', remove(d, 2)) @@ -301,11 +336,12 @@ func Test_dict_deepcopy() let l = [4, d, 6] let d[3] = l let dc = deepcopy(d) - call assert_fails('call deepcopy(d, 1)', 'E698') + call assert_fails('call deepcopy(d, 1)', 'E698:') let l2 = [0, l, l, 3] let l[1] = l2 let l3 = deepcopy(l2) call assert_true(l3[1] is l3[2]) + call assert_fails("call deepcopy([1, 2], 2)", 'E1023:') endfunc " Locked variables @@ -385,6 +421,11 @@ func Test_list_locked_var() call assert_equal(expected[depth][u][1], ps) endfor endfor + call assert_fails("let x=islocked('a b')", 'E488:') + let mylist = [1, 2, 3] + call assert_fails("let x = islocked('mylist[1:2]')", 'E786:') + let mydict = {'k' : 'v'} + call assert_fails("let x = islocked('mydict.a')", 'E716:') endfunc " Unletting locked variables @@ -574,6 +615,18 @@ func Test_let_lock_list() unlet l endfunc +" Locking part of the list +func Test_let_lock_list_items() + let l = [1, 2, 3, 4] + lockvar l[2:] + call assert_equal(0, islocked('l[0]')) + call assert_equal(1, islocked('l[2]')) + call assert_equal(1, islocked('l[3]')) + call assert_fails('let l[2] = 10', 'E741:') + call assert_fails('let l[3] = 20', 'E741:') + unlet l +endfunc + " lockvar/islocked() triggering script autoloading func Test_lockvar_script_autoload() let old_rtp = &rtp @@ -627,7 +680,10 @@ func Test_reverse_sort_uniq() endif call assert_fails('call reverse("")', 'E899:') - call assert_fails('call uniq([1, 2], {x, y -> []})', 'E882:') + call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:') + call assert_fails("call sort([1, 2], function('min'), 1)", "E715:") + call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:") + call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc " reduce a list or a blob @@ -675,7 +731,7 @@ func Test_reduce() call assert_equal(42, reduce(v:_null_blob, function('add'), 42)) endfunc -" splitting a string to a List +" splitting a string to a List using split() func Test_str_split() call assert_equal(['aa', 'bb'], split(' aa bb ')) call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0)) @@ -686,6 +742,8 @@ func Test_str_split() call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1)) call assert_equal(['a', 'b', 'c'], split('abc', '\zs')) call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1)) + call assert_fails("call split('abc', [])", 'E730:') + call assert_fails("call split('abc', 'b', [])", 'E745:') endfunc " compare recursively linked list and dict @@ -697,6 +755,12 @@ func Test_listdict_compare() call assert_true(d == d) call assert_false(l != deepcopy(l)) call assert_false(d != deepcopy(d)) + + " comparison errors + call assert_fails('echo [1, 2] =~ {}', 'E691:') + call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:') + call assert_fails('echo {} =~ 5', 'E735:') + call assert_fails('echo {} =~ {}', 'E736:') endfunc " compare complex recursively linked list and dict @@ -870,6 +934,67 @@ func Test_scope_dict() call s:check_scope_dict('v', v:true) endfunc +" Test for deep nesting of lists (> 100) +func Test_deep_nested_list() + let deep_list = [] + let l = deep_list + for i in range(102) + let newlist = [] + call add(l, newlist) + let l = newlist + endfor + call add(l, 102) + + call assert_fails('let m = deepcopy(deep_list)', 'E698:') + call assert_fails('lockvar 110 deep_list', 'E743:') + call assert_fails('unlockvar 110 deep_list', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_list")', 'E724:') + call test_garbagecollect_now() + unlet deep_list +endfunc + +" Test for deep nesting of dicts (> 100) +func Test_deep_nested_dict() + let deep_dict = {} + let d = deep_dict + for i in range(102) + let newdict = {} + let d.k = newdict + let d = newdict + endfor + let d.k = 'v' + + call assert_fails('let m = deepcopy(deep_dict)', 'E698:') + call assert_fails('lockvar 110 deep_dict', 'E743:') + call assert_fails('unlockvar 110 deep_dict', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_dict")', 'E724:') + call test_garbagecollect_now() + unlet deep_dict +endfunc + +" List and dict indexing tests +func Test_listdict_index() + call assert_fails('echo function("min")[0]', 'E695:') + call assert_fails('echo v:true[0]', 'E909:') + let d = {'k' : 10} + call assert_fails('echo d.', 'E15:') + call assert_fails('echo d[1:2]', 'E719:') + call assert_fails("let v = [4, 6][{-> 1}]", 'E729:') + call assert_fails("let v = range(5)[2:[]]", 'E730:') + call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:']) + call assert_fails("let v = range(5)[2:3", 'E111:') + call assert_fails("let l = insert([1,2,3], 4, 10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, -10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, [])", 'E745:') + let l = [1, 2, 3] + call assert_fails("let l[i] = 3", 'E121:') + call assert_fails("let l[1.1] = 4", 'E806:') + call assert_fails("let l[:i] = [4, 5]", 'E121:') + call assert_fails("let l[:3.2] = [4, 5]", 'E806:') +endfunc + " Test for a null list func Test_null_list() let l = v:_null_list @@ -906,3 +1031,21 @@ func Test_null_list() call assert_equal(1, islocked('l')) unlockvar l endfunc + +" Test for a null dict +func Test_null_dict() + call assert_equal(v:_null_dict, v:_null_dict) + let d = v:_null_dict + call assert_equal({}, d) + call assert_equal(0, len(d)) + call assert_equal(1, empty(d)) + call assert_equal(0, items(d)) + call assert_equal(0, keys(d)) + call assert_equal(0, values(d)) + call assert_false(has_key(d, 'k')) + call assert_equal('{}', string(d)) + call assert_fails('let x = v:_null_dict[10]') + call assert_equal({}, {}) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index bde3624adf..560883ba5d 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -395,7 +395,9 @@ func Test_motionforce_omap() endfunc func Test_error_in_map_expr() - if !has('terminal') || (has('win32') && has('gui_running')) + " Unlike CheckRunVimInTerminal this does work in a win32 console + CheckFeature terminal + if has('win32') && has('gui_running') throw 'Skipped: cannot run Vim in a terminal window' endif @@ -1048,6 +1050,30 @@ func Test_mouse_drag_mapped_start_select() set mouse& endfunc +func Test_mouse_drag_statusline() + set laststatus=2 + set mouse=a + func ClickExpr() + call Ntest_setmouse(&lines - 1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call Ntest_setmouse(&lines - 2, 1) + return "\<LeftDrag>" + endfunc + nnoremap <expr> <F2> ClickExpr() + nnoremap <expr> <F3> DragExpr() + + " this was causing a crash in win_drag_status_line() + call feedkeys("\<F2>:tabnew\<CR>\<F3>", 'tx') + + nunmap <F2> + nunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set laststatus& mouse& +endfunc + " Test for mapping <LeftDrag> in Insert mode func Test_mouse_drag_insert_map() set mouse=a diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 74e63d9d69..b432b7bbbc 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -91,6 +91,17 @@ func Test_setpos() call assert_equal([0, 1, 21341234, 0], getpos("'a")) call assert_equal(4, virtcol("'a")) + " Test with invalid buffer number, line number and column number + call cursor(2, 2) + call setpos('.', [-1, 1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, -1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, 1, -1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + + call assert_fails("call setpos('ab', [0, 1, 1, 0])", 'E474:') + bwipe! call win_gotoid(twowin) bwipe! diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 4f22e54563..5d9be99444 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -160,12 +160,14 @@ endfunc func Test_matchadd_error() call clearmatches() " Nvim: not an error anymore: + " call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:') call matchadd('GroupDoesNotExist', 'X') call assert_equal([{'group': 'GroupDoesNotExist', 'pattern': 'X', 'priority': 10, 'id': 1206}], getmatches()) - call assert_fails("call matchadd('Search', '\\(')", 'E475:') + call assert_fails("call matchadd('Search', '\\(')", 'E54:') call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:') call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:') call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:') + call assert_fails("call matchadd('Error', 'XXX', [], 0)", 'E745:') endfunc func Test_matchaddpos() @@ -305,7 +307,10 @@ func Test_matchaddpos_error() call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:') call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:') " Why doesn't the following error have an error code E...? + " call assert_fails("call matchaddpos('Error', [{}])", 'E290:') call assert_fails("call matchaddpos('Error', [{}])", 'E5031:') + call assert_equal(-1, matchaddpos('Error', v:_null_list)) + call assert_fails("call matchaddpos('Error', [1], [], 1)", 'E745:') endfunc func OtherWindowCommon() @@ -322,9 +327,8 @@ func OtherWindowCommon() endfunc func Test_matchdelete_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchdelete(mid, winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchdelete_1', {}) @@ -339,9 +343,7 @@ func Test_matchdelete_error() endfunc func Test_matchclear_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call clearmatches(winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchclear_1', {}) @@ -351,9 +353,7 @@ func Test_matchclear_other_window() endfunc func Test_matchadd_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\<CR>") call term_sendkeys(buf, ":\<CR>") @@ -363,5 +363,4 @@ func Test_matchadd_other_window() call delete('XscriptMatchCommon') endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim index c836bc87aa..b46550fbc3 100644 --- a/src/nvim/testdir/test_matchfuzzy.vim +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -6,9 +6,7 @@ source check.vim " Test for matchfuzzy() func Test_matchfuzzy() call assert_fails('call matchfuzzy(10, "abc")', 'E686:') - " Needs v8.2.1183; match the final error that's thrown for now - " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') - call assert_fails('call matchfuzzy(["abc"], [])', 'E475:') + call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:') call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:') call assert_equal([], matchfuzzy([], 'abc')) @@ -75,12 +73,9 @@ func Test_matchfuzzy() call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([], matchfuzzy(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions @@ -155,12 +150,9 @@ func Test_matchfuzzypos() call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index db7ec92bf8..a1121632e6 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -153,7 +153,7 @@ func Test_menu_errors() call assert_fails('menu Test.Foo.Bar', 'E327:') call assert_fails('cmenu Test.Foo', 'E328:') call assert_fails('emenu x Test.Foo', 'E475:') - call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('emenu Test.Foo.Bar', 'E327:') call assert_fails('menutranslate Test', 'E474:') silent! unmenu Foo @@ -252,6 +252,7 @@ func Test_menu_info() nmenu Test.abc <Nop> call assert_equal('<Nop>', menu_info('Test.abc').rhs) call assert_fails('call menu_info([])', 'E730:') + call assert_fails('call menu_info("", [])', 'E730:') nunmenu Test " Test for defining menus in different modes @@ -429,7 +430,7 @@ func Test_menu_special() nunmenu Test.Sign endfunc -" Test for "icon=filname" in a toolbar +" Test for "icon=filename" in a toolbar func Test_menu_icon() CheckFeature toolbar nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR> diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim index cdf688b857..057f4a1bea 100644 --- a/src/nvim/testdir/test_method.vim +++ b/src/nvim/testdir/test_method.vim @@ -35,6 +35,7 @@ func Test_list_method() call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) call assert_fails('eval l->values()', 'E715:') + call assert_fails('echo []->len', 'E107:') endfunc func Test_dict_method() @@ -130,9 +131,9 @@ func Test_method_syntax() eval [1, 2, 3] \ ->sort( \ ) - call assert_fails('eval [1, 2, 3]-> sort()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort()', 'E15:') call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') - call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:') endfunc func Test_method_lambda() diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index ccc775560f..9cf80f631c 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -851,9 +851,7 @@ func Test_mksession_shortmess_with_A() edit Xtestfile write let fname = swapname('%') - " readblob() needs patch 8.2.2343 - " let cont = readblob(fname) - let cont = readfile(fname, 'B') + let cont = readblob(fname) set sessionoptions-=options mksession Xtestsession bwipe! diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4f842189b6..84929e2be3 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -120,6 +120,39 @@ func Test_normal01_keymodel() call feedkeys("Vkk\<Up>yy", 'tx') call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1)) + " Test for using special keys to start visual selection + %d + call setline(1, ['red fox tail', 'red fox tail', 'red fox tail']) + set keymodel=startsel + " Test for <S-PageUp> and <S-PageDown> + call cursor(1, 1) + call feedkeys("\<S-PageDown>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 3, 1, 0], getpos("'>")) + call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt') + call assert_equal([0, 2, 1, 0], getpos("'<")) + call assert_equal([0, 3, 8, 0], getpos("'>")) + " Test for <S-C-Home> and <S-C-End> + call cursor(2, 12) + call feedkeys("\<S-C-Home>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 2, 12, 0], getpos("'>")) + call cursor(1, 4) + call feedkeys("\<S-C-End>y", 'xt') + call assert_equal([0, 1, 4, 0], getpos("'<")) + call assert_equal([0, 3, 13, 0], getpos("'>")) + " Test for <S-C-Left> and <S-C-Right> + call cursor(2, 5) + call feedkeys("\<S-C-Right>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + call cursor(2, 9) + call feedkeys("\<S-C-Left>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + + set keymodel& + " clean up bw! endfunc @@ -140,16 +173,15 @@ func Test_normal03_join() $ :j 10 call assert_equal('100', getline('.')) + call assert_beeps('normal GVJ') " clean up bw! endfunc +" basic filter test func Test_normal04_filter() - " basic filter test " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows call Setup_NewWindow() 1 call feedkeys("!!sed -e 's/^/| /'\n", 'tx') @@ -222,12 +254,10 @@ func Test_normal_formatexpr_returns_nonzero() close! endfunc +" basic test for formatprg func Test_normal06_formatprg() - " basic test for formatprg " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows " uses sed to number non-empty lines call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh') @@ -240,16 +270,24 @@ func Test_normal06_formatprg() set formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d - 10new call setline(1, text) set formatprg=donothing setlocal formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d + " Check for the command-line ranges added to 'formatprg' + set formatprg=cat + call setline(1, ['one', 'two', 'three', 'four', 'five']) + call feedkeys('gggqG', 'xt') + call assert_equal('.,$!cat', @:) + call feedkeys('2Ggq2j', 'xt') + call assert_equal('.,.+2!cat', @:) + + bw! " clean up set formatprg= setlocal formatprg= @@ -263,18 +301,16 @@ func Test_normal07_internalfmt() 10new call setline(1, list) set tw=12 - norm! gggqG + norm! ggVGgq call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$')) " clean up set tw=0 bw! endfunc +" basic tests for foldopen/folddelete func Test_normal08_fold() - " basic tests for foldopen/folddelete - if !has("folding") - return - endif + CheckFeature folding call Setup_NewWindow() 50 setl foldenable fdm=marker @@ -506,6 +542,14 @@ func Test_normal10_expand() call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i) endfor + " Test for <cexpr> in state.val and ptr->val + call setline(1, 'x = state.val;') + call cursor(1, 10) + call assert_equal('state.val', expand('<cexpr>')) + call setline(1, 'x = ptr->val;') + call cursor(1, 9) + call assert_equal('ptr->val', expand('<cexpr>')) + if executable('echo') " Test expand(`...`) i.e. backticks command expansion. " MS-Windows has a trailing space. @@ -514,11 +558,25 @@ func Test_normal10_expand() " Test expand(`=...`) i.e. backticks expression expansion call assert_equal('5', expand('`=2+3`')) + call assert_equal('3.14', expand('`=3.14`')) " clean up bw! endfunc +" Test for expand() in latin1 encoding +func Test_normal_expand_latin1() + new + let save_enc = &encoding + " set encoding=latin1 + call setline(1, 'val = item->color;') + call cursor(1, 11) + call assert_equal('color', expand("<cword>")) + call assert_equal('item->color', expand("<cexpr>")) + let &encoding = save_enc + bw! +endfunc + func Test_normal11_showcmd() " test for 'showcmd' 10new @@ -543,6 +601,13 @@ func Test_normal11_showcmd() redraw! call assert_match('1-3$', Screenline(&lines)) call feedkeys("v", 'xt') + " test for visually selecting the end of line + call setline(1, ["foobar"]) + call feedkeys("$vl", 'xt') + redraw! + call assert_match('2$', Screenline(&lines)) + call feedkeys("y", 'xt') + call assert_equal("r\n", @") bw! endfunc @@ -1431,10 +1496,8 @@ func Test_normal18_z_fold() endfunc func Test_normal20_exmode() - if !has("unix") - " Reading from redirected file doesn't work on MS-Windows - return - endif + " Reading from redirected file doesn't work on MS-Windows + CheckNotMSWindows call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') call writefile(['1', '2'], 'Xfile') call system(GetVimCommand() .. ' -e -s < Xscript Xfile') @@ -1587,7 +1650,7 @@ func Test_normal23_K() call setline(1, '---') call assert_fails('normal! ggv2lK', 'E349:') call setline(1, ['abc', 'xyz']) - call assert_fails("normal! gg2lv2h\<C-]>", 'E426:') + call assert_fails("normal! gg2lv2h\<C-]>", 'E433:') call assert_beeps("normal! ggVjK") " clean up @@ -2062,6 +2125,16 @@ func Test_normal30_changecase() call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) set whichwrap& + " try changing the case with a double byte encoding (DBCS) + %bw! + let enc = &enc + " set encoding=cp932 + call setline(1, "\u8470") + normal ~ + normal gU$gu$gUgUg~g~gugu + call assert_equal("\u8470", getline(1)) + let &encoding = enc + " clean up bw! endfunc @@ -2153,6 +2226,19 @@ func Test_normal31_r_cmd() " r command should fail in operator pending mode call assert_beeps('normal! cr') + " replace a tab character in visual mode + %d + call setline(1, ["a\tb", "c\td", "e\tf"]) + normal gglvjjrx + call assert_equal(['axx', 'xxx', 'xxf'], getline(1, '$')) + + " replace with a multibyte character (with multiple composing characters) + %d + new + call setline(1, 'aaa') + exe "normal $ra\u0328\u0301" + call assert_equal("aaa\u0328\u0301", getline(1)) + " clean up set noautoindent bw! @@ -2177,9 +2263,7 @@ endfunc " Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, " gi and gI commands func Test_normal33_g_cmd2() - if !has("jumplist") - return - endif + CheckFeature jumplist call Setup_NewWindow() " Test for g` clearjumps @@ -2638,7 +2722,6 @@ endfunc " Test for cw cW ce func Test_normal39_cw() " Test for cw and cW on whitespace - " and cpo+=w setting new set tw=0 call append(0, 'here are some words') @@ -2646,14 +2729,6 @@ func Test_normal39_cw() call assert_equal('hereZZZare some words', getline('.')) norm! 1gg0elcWYYY call assert_equal('hereZZZareYYYsome words', getline('.')) - " Nvim: no "w" flag in 'cpoptions'. - " set cpo+=w - " call setline(1, 'here are some words') - " norm! 1gg0elcwZZZ - " call assert_equal('hereZZZ are some words', getline('.')) - " norm! 1gg2elcWYYY - " call assert_equal('hereZZZ areYYY some words', getline('.')) - set cpo-=w norm! 2gg0cwfoo call assert_equal('foo', getline('.')) @@ -2848,9 +2923,8 @@ func Test_normal49_counts() endfunc func Test_normal50_commandline() - if !has("timers") || !has("cmdline_hist") - return - endif + CheckFeature timers + CheckFeature cmdline_hist func! DoTimerWork(id) call assert_equal('[Command Line]', bufname('')) " should fail, with E11, but does fail with E23? @@ -2879,9 +2953,7 @@ func Test_normal50_commandline() endfunc func Test_normal51_FileChangedRO() - if !has("autocmd") - return - endif + CheckFeature autocmd call writefile(['foo'], 'Xreadonly.log') new Xreadonly.log setl ro @@ -2896,9 +2968,7 @@ func Test_normal51_FileChangedRO() endfunc func Test_normal52_rl() - if !has("rightleft") - return - endif + CheckFeature rightleft new call setline(1, 'abcde fghij klmnopq') norm! 1gg$ @@ -2930,22 +3000,6 @@ func Test_normal52_rl() bw! endfunc -func Test_normal53_digraph() - if !has('digraphs') - return - endif - new - call setline(1, 'abcdefgh|') - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(9, col('.')) - set cpo+=D - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(1, col('.')) - - set cpo-=D - bw! -endfunc - func Test_normal54_Ctrl_bsl() new call setline(1, 'abcdefghijklmn') @@ -3266,46 +3320,6 @@ func Test_normal_gk_gj() set cpoptions& number& numberwidth& wrap& endfunc -" Test for cursor movement with '-' in 'cpoptions' -func Test_normal_cpo_minus() - throw 'Skipped: Nvim does not support cpoptions flag "-"' - new - call setline(1, ['foo', 'bar', 'baz']) - let save_cpo = &cpo - set cpo+=- - call assert_beeps('normal 10j') - call assert_equal(1, line('.')) - normal G - call assert_beeps('normal 10k') - call assert_equal(3, line('.')) - call assert_fails(10, 'E16:') - let &cpo = save_cpo - close! -endfunc - -" Test for displaying dollar when changing text ('$' flag in 'cpoptions') -func Test_normal_cpo_dollar() - throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' - new - let g:Line = '' - func SaveFirstLine() - let g:Line = Screenline(1) - return '' - endfunc - inoremap <expr> <buffer> <F2> SaveFirstLine() - call test_override('redraw_flag', 1) - set cpo+=$ - call setline(1, 'one two three') - redraw! - exe "normal c2w\<F2>vim" - call assert_equal('one tw$ three', g:Line) - call assert_equal('vim three', getline(1)) - set cpo-=$ - call test_override('ALL', 0) - delfunc SaveFirstLine - %bw! -endfunc - " Test for using : to run a multi-line Ex command in operator pending mode func Test_normal_yank_with_excmd() new @@ -3370,6 +3384,24 @@ func Test_normal_colon_op() close! endfunc +" Test for d and D commands +func Test_normal_delete_cmd() + new + " D in an empty line + call setline(1, '') + normal D + call assert_equal('', getline(1)) + " D in an empty line in virtualedit mode + set virtualedit=all + normal D + call assert_equal('', getline(1)) + set virtualedit& + " delete to a readonly register + call setline(1, ['abcd']) + call assert_beeps('normal ":d2l') + close! +endfunc + " Test for deleting or changing characters across lines with 'whichwrap' " containing 's'. Should count <EOL> as one character. func Test_normal_op_across_lines() @@ -3477,6 +3509,27 @@ func Test_normal_percent_jump() close! endfunc +" Test for << and >> commands to shift text by 'shiftwidth' +func Test_normal_shift_rightleft() + new + call setline(1, ['one', '', "\t", ' two', "\tthree", ' four']) + set shiftwidth=2 tabstop=8 + normal gg6>> + call assert_equal([' one', '', "\t ", ' two', "\t three", "\tfour"], + \ getline(1, '$')) + normal ggVG2>> + call assert_equal([' one', '', "\t ", "\ttwo", + \ "\t three", "\t four"], getline(1, '$')) + normal gg6<< + call assert_equal([' one', '', "\t ", ' two', "\t three", + \ "\t four"], getline(1, '$')) + normal ggVG2<< + call assert_equal(['one', '', "\t", ' two', "\tthree", ' four'], + \ getline(1, '$')) + set shiftwidth& tabstop& + bw! +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index ada6d2406b..3916d7c554 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -263,7 +263,7 @@ func Test_set_completion() call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:) - " Expand boolan options. When doing :set no<Tab> + " Expand boolean options. When doing :set no<Tab> " vim displays the options names without "no" but completion uses "no...". call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set nodiff digraph', @:) @@ -432,9 +432,8 @@ endfunc " Must be executed before other tests that set 'term'. func Test_000_term_option_verbose() - if has('nvim') || has('gui_running') - return - endif + throw "Skipped: Nvim does not support setting 'term'" + CheckNotGui call CheckWasNotSet('t_cm') @@ -1117,6 +1116,35 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm' +func Test_VIM_POSIX() + throw 'Skipped: Nvim does not support $VIM_POSIX' + let saved_VIM_POSIX = getenv("VIM_POSIX") + + call setenv('VIM_POSIX', "1") + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;', + \ 'AS'], readfile('X_VIM_POSIX')) + endif + + call setenv('VIM_POSIX', v:null) + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;', + \ 'S'], readfile('X_VIM_POSIX')) + endif + + call delete('X_VIM_POSIX') + call setenv('VIM_POSIX', saved_VIM_POSIX) +endfunc + " Test for setting an option to a Vi or Vim default func Test_opt_default() throw 'Skipped: Nvim has different defaults' diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 8c90f21600..3020668f1b 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -82,6 +82,9 @@ func Test_partial_dict() let dict = {"tr": function('tr', ['hello', 'h', 'H'])} call assert_equal("Hello", dict.tr()) + + call assert_fails("let F=function('setloclist', 10)", "E923:") + call assert_fails("let F=function('setloclist', [], [])", "E922:") endfunc func Test_partial_implicit() @@ -354,3 +357,5 @@ func Test_compare_partials() call assert_true(F1 isnot# F1d1) " Partial /= non-partial call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 067e5d14e5..7f183f0849 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -677,9 +677,9 @@ func Test_complete_CTRLN_startofbuffer() endfunc func Test_popup_and_window_resize() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + let h = winheight(0) if h < 15 return @@ -948,9 +948,9 @@ func Test_complete_o_tab() endfunc func Test_menu_only_exists_in_terminal() - if !exists(':tlmenu') || has('gui_running') - return - endif + CheckCommand tlmenu + CheckNotGui + tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+ aunmenu * try diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b0e83b7f5f..9d9fc5e77b 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -740,7 +740,7 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') " Invalid regular expression - call assert_fails('Xhelpgrep \@<!', 'E480:') + call assert_fails('Xhelpgrep \@<!', 'E866:') endfunc func Test_helpgrep() @@ -3251,7 +3251,7 @@ func Test_cclose_in_autocmd() " call test_override('starting', 0) endfunc -" Check that ":file" without an argument is possible even when curbuf is locked +" Check that ":file" without an argument is possible even when "curbuf->b_ro_locked" " is set. func Test_file_from_copen() " Works without argument. @@ -4101,8 +4101,8 @@ endfunc func Test_lvimgrep_crash2() au BufNewFile x sfind - call assert_fails('lvimgrep x x', 'E480:') - call assert_fails('lvimgrep x x x', 'E480:') + call assert_fails('lvimgrep x x', 'E471:') + call assert_fails('lvimgrep x x x', 'E471:') au! BufNewFile endfunc @@ -5801,6 +5801,21 @@ func Test_win_gettype() lclose endfunc +fun Test_vimgrep_nomatch() + call XexprTests('c') + call g:Xsetlist([{'lnum':10,'text':'Line1'}]) + copen + if has("win32") + call assert_fails('vimgrep foo *.zzz', 'E479:') + let expected = [{'lnum': 10, 'bufnr': 0, 'end_lnum': 0, 'pattern': '', 'valid': 0, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 0, 'text': 'Line1'}] + else + call assert_fails('vimgrep foo *.zzz', 'E480:') + let expected = [] + endif + call assert_equal(expected, getqflist()) + cclose +endfunc + " Test for opening the quickfix window in two tab pages and then closing one " of the quickfix windows. This should not make the quickfix buffer unlisted. " (github issue #9300). diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim index 6d3f7dcfd9..fb1c9ab6cd 100644 --- a/src/nvim/testdir/test_random.vim +++ b/src/nvim/testdir/test_random.vim @@ -33,11 +33,11 @@ func Test_Rand() endif call assert_fails('echo srand([1])', 'E745:') call assert_fails('echo rand("burp")', 'E475:') - call assert_fails('echo rand([1, 2, 3])', 'E475:') - call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') - call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') - call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') - call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') + call assert_fails('echo rand([1, 2, 3])', 'E730:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E730:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E730:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E730:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E730:') " call test_settime(0) endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index d08a980787..ece6ae518e 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -101,8 +101,33 @@ func Test_multi_failure() set re=2 call assert_fails('/a**', 'E871:') call assert_fails('/a*\+', 'E871:') - call assert_fails('/a\{a}', 'E870:') + call assert_fails('/a\{a}', 'E554:') + set re=0 +endfunc + +func Test_column_success_failure() + new + call setline(1, 'xbar') + + set re=1 + %s/\%>0v./A/ + call assert_equal('Abar', getline(1)) + call assert_fails('/\%v', 'E71:') + call assert_fails('/\%>v', 'E71:') + call assert_fails('/\%c', 'E71:') + call assert_fails('/\%<c', 'E71:') + call assert_fails('/\%l', 'E71:') + set re=2 + %s/\%>0v./B/ + call assert_equal('Bbar', getline(1)) + call assert_fails('/\%v', 'E1273:') + call assert_fails('/\%>v', 'E1273:') + call assert_fails('/\%c', 'E1273:') + call assert_fails('/\%<c', 'E1273:') + call assert_fails('/\%l', 'E1273:') + set re=0 + bwipe! endfunc func Test_recursive_addstate() @@ -146,6 +171,10 @@ func Test_regexp_single_line_pat() call add(tl, [2, 'c*', 'abdef', '']) call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) call add(tl, [2, 'bc\+', 'abdef']) " no match + " match escape character in a string + call add(tl, [2, '.\e.', "one\<Esc>two", "e\<Esc>t"]) + " match backspace character in a string + call add(tl, [2, '.\b.', "one\<C-H>two", "e\<C-H>t"]) " match newline character in a string call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"]) @@ -895,6 +924,8 @@ func Test_regexp_error() call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') + call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:') + call assert_equal('', matchstr('abcd', '\%o181\%o142')) endfunc " Test for using the last substitute string pattern (~) @@ -1027,6 +1058,28 @@ func Test_using_invalid_visual_position() bwipe! endfunc +func Test_using_two_engines_pattern() + new + call setline(1, ['foobar=0', 'foobar=1', 'foobar=2']) + " \%#= at the end of the pattern + for i in range(0, 2) + for j in range(0, 2) + exe "set re=" .. i + call cursor(j + 1, 7) + call assert_fails("%s/foobar\\%#=" .. j, 'E1281:') + endfor + endfor + set re=0 + + " \%#= at the start of the pattern + for i in range(0, 2) + call cursor(i + 1, 7) + exe ":%s/\\%#=" .. i .. "foobar=" .. i .. "/xx" + endfor + call assert_equal(['xx', 'xx', 'xx'], getline(1, '$')) + bwipe! +endfunc + func Test_recursive_substitute_expr() new func Repl() diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 11dd3badb6..5bdbbe7a22 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -263,8 +263,16 @@ func Test_get_register() call assert_equal('', getreg("\<C-F>")) call assert_equal('', getreg("\<C-W>")) call assert_equal('', getreg("\<C-L>")) + " Change the last used register to '"' for the next test + normal! ""yy + let @" = 'happy' + call assert_equal('happy', getreg()) + call assert_equal('happy', getreg('')) call assert_equal('', getregtype('!')) + call assert_fails('echo getregtype([])', 'E730:') + call assert_equal('v', getregtype()) + call assert_equal('v', getregtype('')) " Test for inserting an invalid register content call assert_beeps('exe "normal i\<C-R>!"') @@ -277,7 +285,9 @@ func Test_get_register() " Test for inserting a multi-line register in the command line call feedkeys(":\<C-R>r\<Esc>", 'xt') - call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + " Nvim: no trailing CR because of #6137 + " call assert_equal("a\rb\r", histget(':', -1)) + call assert_equal("a\rb", histget(':', -1)) call assert_fails('let r = getreg("=", [])', 'E745:') call assert_fails('let r = getreg("=", 1, [])', 'E745:') @@ -349,6 +359,12 @@ func Test_set_register() normal 0".gP call assert_equal('abcabcabc', getline(1)) + let @"='' + call setreg('', '1') + call assert_equal('1', @") + call setreg('@', '2') + call assert_equal('2', @") + enew! endfunc diff --git a/src/nvim/testdir/test_reltime.vim b/src/nvim/testdir/test_reltime.vim index b381f1ddbb..f4ce5de118 100644 --- a/src/nvim/testdir/test_reltime.vim +++ b/src/nvim/testdir/test_reltime.vim @@ -24,4 +24,8 @@ func Test_reltime() call assert_true(reltimefloat(differs) < 0.1) call assert_true(reltimefloat(differs) > 0.0) + call assert_equal(0, reltime({})) + call assert_equal(0, reltime({}, {})) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 0cf55c7d0b..96ed35718f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -294,6 +294,9 @@ func Test_searchpair() new call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + " should not give an error for using "42" + call assert_equal(0, searchpair('a', 'b', 'c', '', 42)) + 4 call assert_equal(3, searchpair('\[', '', ']', 'bW')) call assert_equal([0, 3, 2, 0], getpos('.')) @@ -897,9 +900,7 @@ func Test_incsearch_cmdline_modifier() endfunc func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call assert_equal(0, &scrolloff) call writefile([ \ 'let dots = repeat(".", 120)', @@ -1649,8 +1650,8 @@ func Test_search_with_no_last_pat() call assert_fails(";//p", 'E35:') call assert_fails("??p", 'E35:') call assert_fails(";??p", 'E35:') - call assert_fails('g//p', 'E476:') - call assert_fails('v//p', 'E476:') + call assert_fails('g//p', ['E35:', 'E476:']) + call assert_fails('v//p', ['E35:', 'E476:']) call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -1671,8 +1672,8 @@ func Test_search_tilde_pat() call assert_fails('exe "normal /~\<CR>"', 'E33:') call assert_fails('exe "normal ?~\<CR>"', 'E33:') set regexpengine=2 - call assert_fails('exe "normal /~\<CR>"', 'E383:') - call assert_fails('exe "normal ?~\<CR>"', 'E383:') + call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:']) + call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:']) set regexpengine& call writefile(v:errors, 'Xresult') qall! @@ -1752,6 +1753,25 @@ func Test_invalid_regexp() call assert_fails("call search('\\%#=3ab')", 'E864:') endfunc +" Test for searching a very complex pattern in a string. Should switch the +" regexp engine from NFA to the old engine. +func Test_regexp_switch_engine() + let l = readfile('samples/re.freeze.txt') + let v = substitute(l[4], '..\@<!', '', '') + call assert_equal(l[4], v) +endfunc + +" Test for the \%V atom to search within visually selected text +func Test_search_in_visual_area() + new + call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4']) + exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>" + call assert_equal([2, 5], [line('.'), col('.')]) + exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>" + call assert_equal([3, 5], [line('.'), col('.')]) + close! +endfunc + " Test for searching with 'smartcase' and 'ignorecase' func Test_search_smartcase() new diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim index f2cab45450..041f0592f1 100644 --- a/src/nvim/testdir/test_selectmode.vim +++ b/src/nvim/testdir/test_selectmode.vim @@ -34,6 +34,9 @@ func Test_selectmode_start() set selectmode=cmd call feedkeys('gvabc', 'xt') call assert_equal('abctdef', getline(1)) + " arrow keys without shift should not start selection + call feedkeys("A\<Home>\<Right>\<Left>ro", 'xt') + call assert_equal('roabctdef', getline(1)) set selectmode= keymodel= bw! endfunc diff --git a/src/nvim/testdir/test_signals.vim b/src/nvim/testdir/test_signals.vim index 338c0d79ff..c291c68e0d 100644 --- a/src/nvim/testdir/test_signals.vim +++ b/src/nvim/testdir/test_signals.vim @@ -16,8 +16,9 @@ endfunc " Test signal WINCH (window resize signal) func Test_signal_WINCH() throw 'skipped: Nvim cannot avoid terminal resize' - if has('gui_running') || !HasSignal('WINCH') - return + CheckNotGui + if !HasSignal('WINCH') + throw 'Skipped: WINCH signal not supported' endif " We do not actually want to change the size of the terminal. @@ -52,7 +53,7 @@ endfunc " Test signal PWR, which should update the swap file. func Test_signal_PWR() if !HasSignal('PWR') - return + throw 'Skipped: PWR signal not supported' endif " Set a very large 'updatetime' and 'updatecount', so that we can be sure @@ -78,6 +79,33 @@ func Test_signal_PWR() set updatetime& updatecount& endfunc +" Test signal INT. Handler sets got_int. It should be like typing CTRL-C. +func Test_signal_INT() + CheckRunVimInTerminal + if !HasSignal('INT') + throw 'Skipped: INT signal not supported' + endif + + " Skip the rest of the test when running with valgrind as signal INT is not + " received somehow by Vim when running with valgrind. + let cmd = GetVimCommand() + if cmd =~ 'valgrind' + throw 'Skipped: cannot test signal INT with valgrind' + endif + + let buf = RunVimInTerminal('', {'rows': 6}) + let pid_vim = term_getjob(buf)->job_info().process + + " Check that an endless loop in Vim is interrupted by signal INT. + call term_sendkeys(buf, ":while 1 | endwhile\n") + call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))}) + exe 'silent !kill -s INT ' .. pid_vim + call term_sendkeys(buf, ":call setline(1, 'INTERUPTED')\n") + call WaitForAssert({-> assert_equal('INTERUPTED', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) +endfunc + " Test a deadly signal. " " There are several deadly signals: SISEGV, SIBUS, SIGTERM... @@ -91,9 +119,7 @@ func Test_deadly_signal_TERM() if !HasSignal('TERM') throw 'Skipped: TERM signal not supported' endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let cmd = GetVimCommand() if cmd =~ 'valgrind' throw 'Skipped: cannot test signal TERM with valgrind' @@ -128,8 +154,7 @@ func Test_deadly_signal_TERM() call assert_equal(['foo'], getline(1, '$')) let result = readfile('XautoOut') - call assert_match('VimLeavePre triggered', result[0]) - call assert_match('VimLeave triggered', result[1]) + call assert_equal(["VimLeavePre triggered", "VimLeave triggered"], result) %bwipe! call delete('.Xsig_TERM.swp') diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index ff9ba3d8ed..1f1b3097b1 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -483,13 +483,13 @@ func Test_sign_funcs() call assert_fails('call sign_place(5, "", "sign1", "@", {"lnum" : 10})', \ 'E158:') call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', - \ 'E158:') + \ 'E730:') call assert_fails('call sign_place(21, "", "sign1", "Xsign", \ {"lnum" : -1})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", \ {"lnum" : 0})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : []})', 'E474:') + \ {"lnum" : []})', 'E745:') call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) " Tests for sign_getplaced() @@ -1731,7 +1731,7 @@ func Test_sign_jump_func() call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:') call assert_fails('call sign_jump([], "", "foo")', 'E745:') call assert_fails('call sign_jump(2, [], "foo")', 'E730:') - call assert_fails('call sign_jump(2, "", {})', 'E158:') + call assert_fails('call sign_jump(2, "", {})', 'E731:') call assert_fails('call sign_jump(2, "", "baz")', 'E158:') sign unplace * group=* @@ -1741,9 +1741,7 @@ endfunc " Test for correct cursor position after the sign column appears or disappears. func Test_sign_cursor_position() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let lines =<< trim END call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 9895ad754c..f9cbcbb55f 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -80,7 +80,7 @@ func Test_sort_default() call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], '')) call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 0)) call assert_equal(['2', 'A', 'a', 'AA', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 1)) - call assert_fails('call sort([3.3, 1, "2"], 3)', "E474") + call assert_fails('call sort([3.3, 1, "2"], 3)', "E474:") endfunc " Tests for the ":sort" command. @@ -1360,7 +1360,7 @@ func Test_sort_cmd() call setline(1, ['line1', 'line2']) call assert_fails('sort no', 'E474:') call assert_fails('sort c', 'E475:') - call assert_fails('sort #pat%', 'E682:') + call assert_fails('sort #pat%', 'E654:') enew! endfunc diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim index ba6fd5ad95..0fd923abf2 100644 --- a/src/nvim/testdir/test_source.vim +++ b/src/nvim/testdir/test_source.vim @@ -87,4 +87,10 @@ func Test_source_autocmd_sfile() call delete('Xscript.vim') endfunc +func Test_source_error() + call assert_fails('scriptencoding utf-8', 'E167:') + call assert_fails('finish', 'E168:') + " call assert_fails('scriptversion 2', 'E984:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e421d21de1..d8495fdb9b 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -474,6 +474,35 @@ func Test_spellsuggest_option_expr() bwipe! endfunc +" Test for 'spellsuggest' expr errrors +func Test_spellsuggest_expr_errors() + " 'spellsuggest' + func MySuggest() + return range(3) + endfunc + set spell spellsuggest=expr:MySuggest() + call assert_equal([], spellsuggest('baord', 3)) + + " Test for 'spellsuggest' expression returning a non-list value + func! MySuggest2() + return 'good' + endfunc + set spellsuggest=expr:MySuggest2() + call assert_equal([], spellsuggest('baord')) + + " Test for 'spellsuggest' expression returning a list with dict values + func! MySuggest3() + return [[{}, {}]] + endfunc + set spellsuggest=expr:MySuggest3() + call assert_fails("call spellsuggest('baord')", 'E731:') + + set nospell spellsuggest& + delfunc MySuggest + delfunc MySuggest2 + delfunc MySuggest3 +endfunc + func Test_spellsuggest_timeout() set spellsuggest=timeout:30 set spellsuggest=timeout:-123 @@ -1401,3 +1430,5 @@ let g:test_data_aff_sal = [ \"SAL ZZ- _", \"SAL Z S", \ ] + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index b30a5e7edb..30bbd355df 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -267,10 +267,9 @@ endfunc " Test the -V[N] argument to set the 'verbose' option to [N] func Test_V_arg() - if has('gui_running') - " Can't catch the output of gvim. - return - endif + " Can't catch the output of gvim. + CheckNotGui + let out = system(GetVimCommand() . ' --clean -es -X -V0 -c "set verbose?" -cq') call assert_equal(" verbose=0\n", out) @@ -543,10 +542,9 @@ endfunc func Test_invalid_args() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui for opt in ['-Y', '--does-not-exist'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") @@ -622,6 +620,12 @@ func Test_invalid_args() endfor if has('gui_gtk') + let out = split(system(GetVimCommand() .. ' --socketid'), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "--socketid"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + for opt in ['--socketid x', '--socketid 0xg'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") call assert_equal(1, v:shell_error) @@ -629,6 +633,7 @@ func Test_invalid_args() call assert_equal('Invalid argument for: "--socketid"', out[1]) call assert_equal('More info with: "vim -h"', out[2]) endfor + endif endfunc @@ -747,10 +752,9 @@ func Test_progpath() endfunc func Test_silent_ex_mode() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui " This caused an ml_get error. let out = system(GetVimCommand() . ' -u NONE -es -c''set verbose=1|h|exe "%norm\<c-y>\<c-d>"'' -c cq') @@ -758,10 +762,9 @@ func Test_silent_ex_mode() endfunc func Test_default_term() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui let save_term = $TERM let $TERM = 'unknownxxx' @@ -796,10 +799,17 @@ func Test_zzz_startinsert() call delete('Xtestout') endfunc +func Test_issue_3969() + " Can't catch the output of gvim. + CheckNotGui + + " Check that message is not truncated. + let out = system(GetVimCommand() . ' -es -X -V1 -c "echon ''hello''" -cq') + call assert_equal('hello', out) +endfunc + func Test_start_with_tabs() - if !CanRunVimInTerminal() - return - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('-p a b c', {}) call VerifyScreenDump(buf, 'Test_start_with_tabs', {}) @@ -956,9 +966,7 @@ endfunc " Test for specifying a non-existing vimrc file using "-u" func Test_missing_vimrc() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let after =<< trim [CODE] call assert_match('^E282:', v:errmsg) call writefile(v:errors, 'Xtestout') diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index bb4304396e..2ee6ecc41d 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -63,9 +63,7 @@ func Test_read_fifo_utf8() endfunc func Test_detect_ambiwidth() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Use the title termcap entries to output the escape sequence. call writefile([ diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 88a3c13d65..5b476ddd7f 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -446,13 +446,18 @@ func Test_substitute_errors() call assert_fails('s/FOO/bar/', 'E486:') call assert_fails('s/foo/bar/@', 'E488:') - call assert_fails('s/\(/bar/', 'E476:') + call assert_fails('s/\(/bar/', 'E54:') call assert_fails('s afooabara', 'E146:') call assert_fails('s\\a', 'E10:') setl nomodifiable call assert_fails('s/foo/bar/', 'E21:') + call assert_fails("let s=substitute([], 'a', 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', [], 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', [], 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', 'A', [])", 'E730:') + bwipe! endfunc @@ -488,6 +493,9 @@ func Test_sub_replace_1() call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", '')) call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', '')) call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', '')) + " \v or \V after $ + call assert_equal('abxx', substitute('abcd', 'xy$\v|cd$', 'xx', '')) + call assert_equal('abxx', substitute('abcd', 'xy$\V\|cd\$', 'xx', '')) endfunc func Test_sub_replace_2() @@ -833,9 +841,9 @@ endfunc func Test_sub_with_no_last_pat() let lines =<< trim [SCRIPT] call assert_fails('~', 'E33:') - call assert_fails('s//abc/g', 'E476:') - call assert_fails('s\/bar', 'E476:') - call assert_fails('s\&bar&', 'E476:') + call assert_fails('s//abc/g', 'E35:') + call assert_fails('s\/bar', 'E35:') + call assert_fails('s\&bar&', 'E33:') call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -862,6 +870,40 @@ endfunc func Test_substitute() call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) + " Substitute with special keys + call assert_equal("a\<End>c", substitute('abc', "a.c", "a\<End>c", '')) +endfunc + +func Test_substitute_expr() + let g:val = 'XXX' + call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) + call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ '\=nr2char("0x" . submatch(1))', 'g')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ {-> nr2char("0x" . submatch(1))}, 'g')) + + call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', + \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) + + func Recurse() + return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') + endfunc + " recursive call works + call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) + + call assert_fails("let s=submatch([])", 'E745:') + call assert_fails("let s=submatch(2, [])", 'E745:') +endfunc + +func Test_invalid_submatch() + " This was causing invalid memory access in Vim-7.4.2232 and older + call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') + call assert_fails('eval submatch(-1)', 'E935:') + call assert_equal('', submatch(0)) + call assert_equal('', submatch(1)) + call assert_equal([], submatch(0, 1)) + call assert_equal([], submatch(1, 1)) endfunc func Test_submatch_list_concatenate() @@ -870,6 +912,44 @@ func Test_submatch_list_concatenate() call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") endfunc +func Test_substitute_expr_arg() + call assert_equal('123456789-123456789=', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456-123456=789', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456789-123456789x=', substitute('123456789', + \ '\(.\)\(.\)\(.*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') +endfunc + +" Test for using a function to supply the substitute string +func Test_substitute_using_func() + func Xfunc() + return '1234' + endfunc + call assert_equal('a1234f', substitute('abcdef', 'b..e', + \ function("Xfunc"), '')) + delfunc Xfunc +endfunc + +" Test for using submatch() with a multiline match +func Test_substitute_multiline_submatch() + new + call setline(1, ['line1', 'line2', 'line3', 'line4']) + %s/^line1\(\_.\+\)line4$/\=submatch(1)/ + call assert_equal(['', 'line2', 'line3', ''], getline(1, '$')) + close! +endfunc + func Test_substitute_skipped_range() new if 0 diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index ccff01486e..9d108c6ca2 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -113,6 +113,9 @@ func Test_syntime() let a = execute('syntime report') call assert_equal("\nNo Syntax items defined for this buffer", a) + let a = execute('syntime clear') + call assert_equal("\nNo Syntax items defined for this buffer", a) + view samples/memfile_test.c setfiletype cpp redraw @@ -171,6 +174,10 @@ func Test_syntax_list() let a = execute('syntax list') call assert_equal("\nNo Syntax items defined for this buffer", a) + syntax keyword Type int containedin=g1 skipwhite skipempty skipnl nextgroup=Abc + let exp = "Type xxx containedin=g1 nextgroup=Abc skipnl skipwhite skipempty int" + call assert_equal(exp, split(execute("syntax list"), "\n")[1]) + bd endfunc @@ -347,6 +354,18 @@ func Test_syntax_arg_skipped() syn clear endfunc +" Check for an error. Used when multiple errors are thrown and we are checking +" for an earliest error. +func AssertFails(cmd, errcode) + let save_exception = '' + try + exe a:cmd + catch + let save_exception = v:exception + endtry + call assert_match(a:errcode, save_exception) +endfunc + func Test_syntax_invalid_arg() call assert_fails('syntax case asdf', 'E390:') if has('conceal') @@ -354,11 +373,49 @@ func Test_syntax_invalid_arg() endif call assert_fails('syntax spell asdf', 'E390:') call assert_fails('syntax clear @ABCD', 'E391:') - call assert_fails('syntax include @Xxx', 'E397:') - call assert_fails('syntax region X start="{"', 'E399:') + call assert_fails('syntax include random_file', 'E484:') + call assert_fails('syntax include <afile>', 'E495:') call assert_fails('syntax sync x', 'E404:') call assert_fails('syntax keyword Abc a[', 'E789:') call assert_fails('syntax keyword Abc a[bc]d', 'E890:') + call assert_fails('syntax cluster Abc add=A add=', 'E406:') + + " Test for too many \z\( and unmatched \z\( + " Not able to use assert_fails() here because both E50:/E879: and E475: + " messages are emitted. + set regexpengine=1 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E52:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E50:') + + set regexpengine=2 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E54:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E879:') + set regexpengine& + + call AssertFails('syntax keyword cMyItem grouphere G1', 'E393:') + call AssertFails('syntax sync match Abc grouphere MyItem "abc"', 'E394:') + call AssertFails('syn keyword Type contains int', 'E395:') + call assert_fails('syntax include @Xxx', 'E397:') + call AssertFails('syntax region X start', 'E398:') + call assert_fails('syntax region X start="{"', 'E399:') + call AssertFails('syntax cluster contains=Abc', 'E400:') + call AssertFails("syntax match Character /'.'", 'E401:') + call AssertFails("syntax match Character /'.'/a", 'E402:') + call assert_fails('syntax sync linecont /pat', 'E404:') + call assert_fails('syntax sync linecont', 'E404:') + call assert_fails('syntax sync linecont /pat1/ linecont /pat2/', 'E403:') + call assert_fails('syntax sync minlines=a', 'E404:') + call AssertFails('syntax match ABC /x/ contains=', 'E406:') + call AssertFails("syntax match Character contains /'.'/", 'E405:') + call AssertFails('syntax match ccFoo "Foo" nextgroup=ALLBUT,F', 'E407:') + call AssertFails('syntax region Block start="{" contains=F,ALLBUT', 'E408:') + call AssertFails("syntax match Characters contains=a.*x /'.'/", 'E409:') endfunc func Test_syn_sync() @@ -386,6 +443,7 @@ func Test_syn_clear() hi clear Foo call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) hi clear Bar + call assert_fails('syntax clear invalid_syngroup', 'E28:') endfunc func Test_invalid_name() @@ -479,15 +537,16 @@ func Test_conceal() call assert_match('16 ', ScreenLines(2, 7)[0]) call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + call AssertFails("syntax match Entity '&' conceal cchar=\<Tab>", 'E844:') + syn clear set conceallevel& bw! endfunc func Test_bg_detection() - if has('gui_running') - return - endif + CheckNotGui + " auto-detection of &bg, make sure sure it isn't set anywhere before " this test hi Normal ctermbg=0 @@ -564,15 +623,15 @@ func Test_synstack_synIDtrans() call assert_equal(['cComment', 'cTodo'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) call assert_equal(['Comment', 'Todo'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + call assert_fails("let n=synIDtrans([])", 'E745:') + syn clear bw! endfunc " Check highlighting for a small piece of C code with a screen dump. func Test_syntax_c() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ '/* comment line at the top */', \ 'int main(int argc, char **argv) { // another comment', @@ -607,6 +666,24 @@ func Test_syntax_c() call delete('Xtest.c') endfun +" Test \z(...) along with \z1 +func Test_syn_zsub() + new + syntax on + call setline(1, 'xxx start foo xxx not end foo xxx end foo xxx') + let l:expected = ' ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ' + + for l:re in [0, 1, 2] + " Example taken from :help :syn-ext-match + syntax region Z start="start \z(\I\i*\)" skip="not end \z1" end="end \z1" + eval AssertHighlightGroups(1, 1, l:expected, 1, 'regexp=' .. l:re) + syntax clear Z + endfor + + set re& + bw! +endfunc + " Using \z() in a region with NFA failing should not crash. func Test_syn_wrong_z_one() new diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 6d468ec9de..b97aa409d8 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -591,9 +591,7 @@ func Test_tabs() endfunc func Test_tabpage_cmdheight() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ 'set laststatus=2', \ 'set cmdheight=2', @@ -623,6 +621,17 @@ func Test_tabpage_close_cmdwin() tabonly endfunc +" Pressing <C-PageUp> in insert mode should go to the previous tab page +" and <C-PageDown> should go to the next tab page +func Test_tabpage_Ctrl_Pageup() + tabnew + call feedkeys("i\<C-PageUp>", 'xt') + call assert_equal(1, tabpagenr()) + call feedkeys("i\<C-PageDown>", 'xt') + call assert_equal(2, tabpagenr()) + %bw! +endfunc + " Return the terminal key code for selecting a tab page from the tabline. This " sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0), " KS_FILLER (0x58) and then the tab page number. diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index ffc1d63b90..bdf5afa5b2 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -85,7 +85,7 @@ func Test_tagfunc() return v:null endfunc set tags= tfu=NullTagFunc - call assert_fails('tag nothing', 'E426') + call assert_fails('tag nothing', 'E433') delf NullTagFunc bwipe! diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 04c0218f74..61bf9e6d0d 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -8,7 +8,7 @@ func Test_ptag_with_notagstack() CheckFeature quickfix set notagstack - call assert_fails('ptag does_not_exist_tag_name', 'E426') + call assert_fails('ptag does_not_exist_tag_name', 'E433') set tagstack&vim endfunc @@ -282,6 +282,7 @@ func Test_tag_file_encoding() call delete('Xtags1') endfunc +" Test for emacs-style tags file (TAGS) func Test_tagjump_etags() if !has('emacs_tags') return @@ -345,7 +346,7 @@ func Test_tagjump_etags() \ "Xmain.c,64", \ ";;;;\x7f1,0", \ ], 'Xtags') - call assert_fails('tag foo', 'E426:') + call assert_fails('tag foo', 'E431:') call delete('Xtags') call delete('Xtags2') @@ -371,6 +372,7 @@ func Test_getsettagstack() call assert_fails("call settagstack(1, {'items' : 10})", 'E714') call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928') call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962') + call assert_equal(-1, settagstack(0, v:_null_dict)) set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", @@ -1099,7 +1101,7 @@ func Test_tselect_listing() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", - \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"], + \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"], \ 'Xtags') set tags=Xtags @@ -1422,4 +1424,56 @@ func Test_tag_length() set tags& taglength& endfunc +" Tests for errors in a tags file +func Test_tagfile_errors() + set tags=Xtags + + " missing search pattern or line number for a tag + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t"], 'Xtags', 'b') + call writefile(['foo'], 'Xfile') + + enew + tag foo + call assert_equal('', @%) + let caught_431 = v:false + try + eval taglist('.*') + catch /:E431:/ + let caught_431 = v:true + endtry + call assert_equal(v:true, caught_431) + + call delete('Xtags') + call delete('Xfile') + set tags& +endfunc + +" When :stag fails to open the file, should close the new window +func Test_stag_close_window_on_error() + new | only + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t1"], 'Xtags') + call writefile(['foo'], 'Xfile') + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup StagTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + + stag foo + call assert_equal(1, winnr('$')) + call assert_equal('', @%) + + augroup StagTest + au! + augroup END + call delete('Xfile') + call delete('.Xfile.swp') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index be46773256..658485582c 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -36,6 +36,8 @@ func Test_taglist() call assert_equal('d', cmd[0]['kind']) call assert_equal('call cursor(3, 4)', cmd[0]['cmd']) + call assert_fails("let l=taglist([])", 'E730:') + call delete('Xtags') set tags& bwipe @@ -82,13 +84,11 @@ func Test_taglist_ctags_etags() endfunc func Test_tags_too_long() - call assert_fails('tag ' . repeat('x', 1020), 'E426') + call assert_fails('tag ' . repeat('x', 1020), ['E433', 'E426']) tags endfunc func Test_tagfiles() - " Nvim: different default for 'tags'. - set tags=./tags,tags call assert_equal([], tagfiles()) call writefile(["FFoo\tXfoo\t1"], 'Xtags1') diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 4eb6e69adf..3bce2eb24d 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -1096,6 +1096,20 @@ func Test_fo_a_w() call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt') call assert_equal(['abc abcde ', 'a abc'], getline(1, '$')) + " when a line ends with space, it is not broken up. + %d + call feedkeys("ione two to ", 'xt') + call assert_equal('one two to ', getline(1)) + + " when a line ends with spaces and backspace is used in the next line, the + " last space in the previous line should be removed. + %d + set backspace=indent,eol,start + call setline(1, ['one ', 'two']) + exe "normal 2Gi\<BS>" + call assert_equal(['one two'], getline(1, '$')) + set backspace& + " Test for 'a', 'w' and '1' options. setlocal textwidth=0 setlocal fo=1aw diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 4d450e180b..f94ee6c9f3 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -3,6 +3,7 @@ source check.vim CheckFeature timers +source screendump.vim source shared.vim source term_util.vim source load.vim @@ -263,8 +264,9 @@ endfunc func Test_timer_peek_and_get_char() if !has('unix') && !has('gui_running') - return + throw 'Skipped: cannot feed low-level input' endif + call timer_start(0, 'FeedAndPeek') let intr = timer_start(100, 'Interrupt') let c = getchar() @@ -274,9 +276,9 @@ endfunc func Test_timer_getchar_zero() if has('win32') && !has('gui_running') - " Console: no low-level input - return + throw 'Skipped: cannot feed low-level input' endif + CheckFunction reltimefloat " Measure the elapsed time to avoid a hang when it fails. let start = reltime() @@ -302,9 +304,7 @@ func Test_timer_ex_mode() endfunc func Test_timer_restore_count() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Check that v:count is saved and restored, not changed by a timer. call writefile([ \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"', @@ -408,6 +408,30 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_changing_function_list() + CheckRunVimInTerminal + + " Create a large number of functions. Should get the "more" prompt. + " The typing "G" triggers the timer, which changes the function table. + let lines =<< trim END + for func in map(range(1,99), "'Func' .. v:val") + exe "func " .. func .. "()" + endfunc + endfor + au CmdlineLeave : call timer_start(0, {-> 0}) + END + call writefile(lines, 'XTest_timerchange') + let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10}) + call term_sendkeys(buf, ":fu\<CR>") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))}) + call term_sendkeys(buf, "G") + call WaitForAssert({-> assert_match('E454', term_getline(buf, 9))}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('XTest_timerchange') +endfunc + func Test_timer_using_win_execute_undo_sync() let bufnr1 = bufnr() new diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index d71bb5bbb8..8a1d2d3fa7 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -2000,13 +2000,11 @@ endfunc func Test_try_catch_errors() call assert_fails('throw |', 'E471:') call assert_fails("throw \n ", 'E471:') - call assert_fails('catch abc', 'E603:') + call assert_fails('catch abc', 'E654:') call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') call assert_fails('finally', 'E606:') call assert_fails('try | finally | finally | endtry', 'E607:') - " v8.2.3486 has been ported, but v8.2.1183 hasn't, so E170 appears here. - " call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') - call assert_fails('try | for i in range(5) | endif | endtry', 'E170:') + call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') call assert_fails('try | while v:true | endtry', 'E170:') call assert_fails('try | if v:true | endtry', 'E171:') endfunc @@ -2223,6 +2221,23 @@ func Test_user_command_throw_in_function_call() unlet g:caught endfunc +" Test that after reporting an uncaught exception there is no error for a +" missing :endif +func Test_after_exception_no_endif_error() + function Throw() + throw "Failure" + endfunction + + function Foo() + if 1 + call Throw() + endif + endfunction + call assert_fails('call Foo()', ['E605:', 'E605:']) + delfunc Throw + delfunc Foo +endfunc + " Test for using throw in a called function with following endtry {{{1 func Test_user_command_function_call_with_endtry() let lines =<< trim END diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index b02bdaab3b..4779d17906 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -1,12 +1,9 @@ " Tests for :unlet func Test_read_only() - try - " this caused a crash - unlet v:count - catch - call assert_true(v:exception =~ ':E795:') - endtry + " these caused a crash + call assert_fails('unlet v:count', 'E795:') + call assert_fails('unlet v:errmsg', 'E795:') endfunc func Test_existing() @@ -18,15 +15,19 @@ endfunc func Test_not_existing() unlet! does_not_exist - try - unlet does_not_exist - catch - call assert_true(v:exception =~ ':E108:') - endtry + call assert_fails('unlet does_not_exist', 'E108:') endfunc func Test_unlet_fails() call assert_fails('unlet v:["count"]', 'E46:') + call assert_fails('unlet $', 'E475:') + let v = {} + call assert_fails('unlet v[:]', 'E719:') + let l = [] + call assert_fails("unlet l['k'", 'E111:') + let d = {'k' : 1} + call assert_fails("unlet d.k2", 'E716:') + call assert_fails("unlet {a};", 'E488:') endfunc func Test_unlet_env() @@ -62,3 +63,5 @@ func Test_unlet_complete() call feedkeys(":unlet $FOO\t\n", 'tx') call assert_true(!exists('$FOOBAR') || empty($FOOBAR)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index c14624f5b4..7aa21d7816 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -3,6 +3,9 @@ " Also test that a builtin function cannot be replaced. " Also test for regression when calling arbitrary expression. +source check.vim +source shared.vim + func Table(title, ...) let ret = a:title let idx = 1 @@ -83,10 +86,14 @@ func Test_user_func() normal o[(one again call assert_equal('1. one again', getline('.')) + " Try to overwrite a function in the global (g:) scope call assert_equal(3, max([1, 2, 3])) call assert_fails("call extend(g:, {'max': function('min')})", 'E704') call assert_equal(3, max([1, 2, 3])) + " Try to overwrite an user defined function with a function reference + call assert_fails("let Expr1 = function('min')", 'E705:') + " Regression: the first line below used to throw ?E110: Missing ')'? " Second is here just to prove that this line is correct when not skipping " rhs of &&. @@ -142,8 +149,8 @@ func Test_default_arg() 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') + call assert_fails("call MakeBadFunc()", 'E989:') + call assert_fails("fu F(a=1 ,) | endf", 'E1068:') " 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. @@ -175,4 +182,310 @@ func Test_function_list() call assert_fails("function Xabc", 'E123:') endfunc +" Test for <sfile>, <slnum> in a function +func Test_sfile_in_function() + func Xfunc() + call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) + call assert_equal('2', expand('<slnum>')) + endfunc + call Xfunc() + delfunc Xfunc +endfunc + +" Test trailing text after :endfunction {{{1 +func Test_endfunction_trailing() + call assert_false(exists('*Xtest')) + + exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + " trailing line break + exe "func Xtest()\necho 'hello'\nendfunc\n" + call assert_true(exists('*Xtest')) + delfunc Xtest + + set verbose=1 + exe "func Xtest()\necho 'hello'\nendfunc \" garbage" + call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + + exe "func Xtest()\necho 'hello'\nendfunc garbage" + call assert_match('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + set verbose=0 + + func Xtest(a1, a2) + echo a:a1 .. a:a2 + endfunc + set verbose=15 + redir @a + call Xtest(123, repeat('x', 100)) + redir END + call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a')) + delfunc Xtest + set verbose=0 + + function Foo() + echo 'hello' + endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + delfunc Foo +endfunc + +func Test_delfunction_force() + delfunc! Xtest + delfunc! Xtest + func Xtest() + echo 'nothing' + endfunc + delfunc! Xtest + delfunc! Xtest + + " Try deleting the current function + call assert_fails('delfunc Test_delfunction_force', 'E131:') +endfunc + +func Test_function_defined_line() + CheckNotGui + + let lines =<< trim [CODE] + " F1 + func F1() + " F2 + func F2() + " + " + " + return + endfunc + " F3 + execute "func F3()\n\n\n\nreturn\nendfunc" + " F4 + execute "func F4()\n + \\n + \\n + \\n + \return\n + \endfunc" + endfunc + " F5 + execute "func F5()\n\n\n\nreturn\nendfunc" + " F6 + execute "func F6()\n + \\n + \\n + \\n + \return\n + \endfunc" + call F1() + verbose func F1 + verbose func F2 + verbose func F3 + verbose func F4 + verbose func F5 + verbose func F6 + qall! + [CODE] + + call writefile(lines, 'Xtest.vim') + let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') + call assert_equal(0, v:shell_error) + + let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') + call assert_match(' line 2$', m) + + let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') + call assert_match(' line 4$', m) + + let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') + call assert_match(' line 11$', m) + + let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') + call assert_match(' line 13$', m) + + let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') + call assert_match(' line 21$', m) + + let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') + call assert_match(' line 23$', m) + + call delete('Xtest.vim') +endfunc + +" Test for defining a function reference in the global scope +func Test_add_funcref_to_global_scope() + let x = g: + let caught_E862 = 0 + try + func x.Xfunc() + return 1 + endfunc + catch /E862:/ + let caught_E862 = 1 + endtry + call assert_equal(1, caught_E862) +endfunc + +func Test_funccall_garbage_collect() + func Func(x, ...) + call add(a:x, a:000) + endfunc + call Func([], []) + " Must not crash cause by invalid freeing + call test_garbagecollect_now() + call assert_true(v:true) + delfunc Func +endfunc + +" Test for script-local function +func <SID>DoLast() + call append(line('$'), "last line") +endfunc + +func s:DoNothing() + call append(line('$'), "nothing line") +endfunc + +func Test_script_local_func() + set nocp nomore viminfo+=nviminfo + new + nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> + + normal _x + call assert_equal('nothing line', getline(2)) + call assert_equal('last line', getline(3)) + close! + + " Try to call a script local function in global scope + let lines =<< trim [CODE] + :call assert_fails('call s:Xfunc()', 'E81:') + :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:') + :call writefile(v:errors, 'Xresult') + :qall + + [CODE] + call writefile(lines, 'Xscript') + if RunVim([], [], '-s Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xresult') + call delete('Xscript') +endfunc + +" Test for errors in defining new functions +func Test_func_def_error() + call assert_fails('func Xfunc abc ()', 'E124:') + call assert_fails('func Xfunc(', 'E125:') + call assert_fails('func xfunc()', 'E128:') + + " Try to redefine a function that is in use + let caught_E127 = 0 + try + func! Test_func_def_error() + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " Try to define a function in a dict twice + let d = {} + let lines =<< trim END + func d.F1() + return 1 + endfunc + END + let l = join(lines, "\n") . "\n" + exe l + call assert_fails('exe l', 'E717:') + + " Define an autoload function with an incorrect file name + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call assert_fails('source Xscript', 'E746:') + call delete('Xscript') +endfunc + +" Test for deleting a function +func Test_del_func() + call assert_fails('delfunction Xabc', 'E130:') + let d = {'a' : 10} + call assert_fails('delfunc d.a', 'E718:') + func d.fn() + return 1 + endfunc + + " cannot delete the dict function by number + let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '') + call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:') + + delfunc d.fn + call assert_equal({'a' : 10}, d) +endfunc + +" Test for calling return outside of a function +func Test_return_outside_func() + call writefile(['return 10'], 'Xscript') + call assert_fails('source Xscript', 'E133:') + call delete('Xscript') +endfunc + +" Test for errors in calling a function +func Test_func_arg_error() + " Too many arguments + call assert_fails("call call('min', range(1,20))", 'E118:') + call assert_fails("call call('min', range(1,21))", 'E699:') + call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)', + \ 'E740:') + + " Missing dict argument + func Xfunc() dict + return 1 + endfunc + call assert_fails('call Xfunc()', 'E725:') + delfunc Xfunc +endfunc + +func Test_func_dict() + let mydict = {'a': 'b'} + function mydict.somefunc() dict + return len(self) + endfunc + + call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict)) + call assert_equal(2, mydict.somefunc()) + call assert_match("^\n function \\d\\\+() dict" + \ .. "\n1 return len(self)" + \ .. "\n endfunction$", execute('func mydict.somefunc')) + call assert_fails('call mydict.nonexist()', 'E716:') +endfunc + +func Test_func_range() + new + call setline(1, range(1, 8)) + func FuncRange() range + echo a:firstline + echo a:lastline + endfunc + 3 + call assert_equal("\n3\n3", execute('call FuncRange()')) + call assert_equal("\n4\n6", execute('4,6 call FuncRange()')) + call assert_equal("\n function FuncRange() range" + \ .. "\n1 echo a:firstline" + \ .. "\n2 echo a:lastline" + \ .. "\n endfunction", + \ execute('function FuncRange')) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 12fe39851d..5b8b384bae 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -617,6 +617,27 @@ func Test_command_list() call assert_equal("\nNo user-defined commands found", execute('command')) endfunc +" Test for a custom user completion returning the wrong value type +func Test_usercmd_custom() + func T1(a, c, p) + return "a\nb\n" + endfunc + command -nargs=* -complete=customlist,T1 TCmd1 + call feedkeys(":TCmd1 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd1 ', @:) + delcommand TCmd1 + delfunc T1 + + func T2(a, c, p) + return {} + endfunc + command -nargs=* -complete=customlist,T2 TCmd2 + call feedkeys(":TCmd2 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd2 ', @:) + delcommand TCmd2 + delfunc T2 +endfunc + func Test_delcommand_buffer() command Global echo 'global' command -buffer OneBuffer echo 'one' diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index ab3503c282..aa3b02b575 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -12,7 +12,7 @@ func Test_visual_block_insert() bwipeout! endfunc -" Test for built-in function strchars() +" Test for built-in functions strchars() and strcharlen() func Test_strchars() let inp = ["a", "あいa", "A\u20dd", "A\u20dd\u20dd", "\u20dd"] let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]] @@ -21,6 +21,15 @@ func Test_strchars() call assert_equal(exp[i][1], inp[i]->strchars(0)) call assert_equal(exp[i][2], strchars(inp[i], 1)) endfor + + let exp = [1, 3, 1, 1, 1] + for i in range(len(inp)) + call assert_equal(exp[i], inp[i]->strcharlen()) + call assert_equal(exp[i], strcharlen(inp[i])) + endfor + + call assert_fails("let v=strchars('abc', [])", 'E745:') + call assert_fails("let v=strchars('abc', 2)", 'E1023:') endfunc " Test for customlist completion @@ -131,9 +140,13 @@ func Test_list2str_str2list_latin1() let save_encoding = &encoding " set encoding=latin1 - + let lres = str2list(s, 1) let sres = list2str(l, 1) + call assert_equal([65, 66, 67], str2list("ABC")) + + " Try converting a list to a string in latin-1 encoding + call assert_equal([1, 2, 3], str2list(list2str([1, 2, 3]))) let &encoding = save_encoding call assert_equal(l, lres) diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 0acd7fc1e5..e12c71d521 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -97,7 +97,7 @@ func Test_vartabs() .retab! call assert_equal("\t\t\t\tl", getline(1)) - " Test for 'retab' with same vlaues as vts + " Test for 'retab' with same values as vts set ts=8 sts=0 vts=5,3,6,2 vsts= exe "norm! S l" .retab! 5,3,6,2 @@ -379,6 +379,8 @@ func Test_vartabs_shiftwidth() let lines = ScreenLines([1, 3], winwidth(0)) call s:compare_lines(expect4, lines) + call assert_fails('call shiftwidth([])', 'E745:') + " cleanup bw! bw! diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 3487a028ca..8faa9135e5 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -3,14 +3,12 @@ source check.vim source shared.vim +source script_util.vim "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- -com! XpathINIT let g:Xpath = '' -com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args> - " Append a message to the "messages" file func Xout(text) split messages @@ -20,67 +18,31 @@ endfunc com! -nargs=1 Xout call Xout(<args>) -" 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 +" Create a new instance of Vim and run the commands in 'test' and then 'verify' +" The commands in 'test' are expected to store the test results in the Xtest.out +" file. If the test passes successfully, then Xtest.out should be empty. +func RunInNewVim(test, verify) + let init =<< trim END + set cpo-=C " support line-continuation in sourced script + source script_util.vim + XpathINIT + XloopINIT + END + let cleanup =<< trim END + call writefile(v:errors, 'Xtest.out') + qall + END + call writefile(init, 'Xtest.vim') + call writefile(a:test, 'Xtest.vim', 'a') + call writefile(a:verify, 'Xverify.vim') + call writefile(cleanup, 'Xverify.vim', 'a') + call RunVim([], [], "-S Xtest.vim -S Xverify.vim") + call assert_equal([], readfile('Xtest.out')) + call delete('Xtest.out') + call delete('Xtest.vim') + call delete('Xverify.vim') endfunc -com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) - - "------------------------------------------------------------------------------- " Test 1: :endwhile in function {{{1 " @@ -90,7 +52,7 @@ com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) " tests will hang. "------------------------------------------------------------------------------- -function! T1_F() +func T1_F() Xpath 'a' let first = 1 while 1 @@ -104,9 +66,9 @@ function! T1_F() return endif endwhile -endfunction +endfunc -function! T1_G() +func T1_G() Xpath 'h' let first = 1 while 1 @@ -121,7 +83,7 @@ function! T1_G() endif if 1 " unmatched :if endwhile -endfunction +endfunc func Test_endwhile_function() XpathINIT @@ -175,7 +137,7 @@ endfunc " Test 3: :if, :elseif, :while, :continue, :break {{{1 "------------------------------------------------------------------------------- -function Test_if_while() +func Test_if_while() XpathINIT if 1 Xpath 'a' @@ -235,7 +197,7 @@ endfunc " Test 4: :return {{{1 "------------------------------------------------------------------------------- -function! T4_F() +func T4_F() if 1 Xpath 'a' let loops = 3 @@ -253,15 +215,15 @@ function! T4_F() else Xpath 'g' endif -endfunction +endfunc -function Test_return() +func Test_return() XpathINIT call T4_F() Xpath '4' call assert_equal('ab3e3b2c24', g:Xpath) -endfunction +endfunc "------------------------------------------------------------------------------- @@ -271,14 +233,14 @@ endfunction " test as a script file (:return replaced by :finish). "------------------------------------------------------------------------------- -function Test_finish() +func Test_finish() XpathINIT ExecAsScript T4_F Xpath '5' call DeleteTheScript() call assert_equal('ab3e3b2c25', g:Xpath) -endfunction +endfunc @@ -412,7 +374,7 @@ delfunction G31 delfunction G32 delfunction G33 -function Test_defining_functions() +func Test_defining_functions() call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result) call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls) endfunc @@ -476,7 +438,7 @@ endfunc XpathINIT -function! T8_F() +func T8_F() if 1 Xpath 'a' while 1 @@ -508,9 +470,9 @@ function! T8_F() return novar " returns (default return value 0) Xpath 'q' return 1 " not reached -endfunction +endfunc -function! T8_G() abort +func T8_G() abort if 1 Xpath 'r' while 1 @@ -524,9 +486,9 @@ function! T8_G() abort Xpath 'x' return -4 " not reached -endfunction +endfunc -function! T8_H() abort +func T8_H() abort while 1 Xpath 'A' if 1 @@ -540,7 +502,7 @@ function! T8_H() abort Xpath 'F' return -4 " not reached -endfunction +endfunc " Aborted functions (T8_G and T8_H) return -1. let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H() @@ -567,7 +529,7 @@ endfunc XpathINIT -function! F() abort +func F() abort Xpath 'a' let result = G() " not aborted Xpath 'b' @@ -575,30 +537,30 @@ function! F() abort Xpath 'c' endif return 1 -endfunction +endfunc -function! G() " no abort attribute +func G() " no abort attribute Xpath 'd' if H() != -1 " aborted Xpath 'e' endif Xpath 'f' return 2 -endfunction +endfunc -function! H() abort +func H() abort Xpath 'g' call I() " aborted Xpath 'h' return 4 -endfunction +endfunc -function! I() abort +func I() abort Xpath 'i' asdf " error Xpath 'j' return 8 -endfunction +endfunc if F() != 1 Xpath 'k' @@ -626,7 +588,7 @@ endfunc XpathINIT -function! MSG(enr, emsg) +func MSG(enr, emsg) let english = v:lang == "C" || v:lang =~ '^[Ee]n' if a:enr == "" Xout "TODO: Add message number for:" a:emsg @@ -710,10 +672,10 @@ XpathINIT let calls = 0 -function! P(num) +func P(num) let g:calls = g:calls + a:num " side effect on call return 0 -endfunction +endfunc if 1 Xpath 'a' @@ -1092,7 +1054,4843 @@ func Test_unmatched_if_in_while() endfunc "------------------------------------------------------------------------------- +" Test 18: Interrupt (Ctrl-C pressed) {{{1 +" +" On an interrupt, the script processing is terminated immediately. +"------------------------------------------------------------------------------- + +func Test_interrupt_while_if() + let test =<< trim [CODE] + try + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + finish + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'd' + endtry | Xpath 'e' + Xpath 'f' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_try() + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry | Xpath 'c' + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_while_if() + let test =<< trim [CODE] + func F() + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + return + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'd' + try + call F() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'e' + endtry | Xpath 'f' + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('dabcefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_try() + let test =<< trim [CODE] + func G() + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'b' + try + call G() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'c' + endtry | Xpath 'd' + Xpath 'e' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bacde', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 19: Aborting on errors inside :try/:endtry {{{1 +" +" An error in a command dynamically enclosed in a :try/:endtry region +" aborts script processing immediately. It does not matter whether +" the failing command is outside or inside a function and whether a +" function has an "abort" attribute. +"------------------------------------------------------------------------------- + +func Test_try_error_abort_1() + let test =<< trim [CODE] + func F() abort + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call F() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_2() + let test =<< trim [CODE] + func G() + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call G() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_3() + let test =<< trim [CODE] + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_4() + let test =<< trim [CODE] + if 1 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_6() + let test =<< trim [CODE] + let p = 1 + Xpath 'a' + while p + Xpath 'b' + let p = 0 + try + Xpath 'c' + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 20: Aborting on errors after :try/:endtry {{{1 +" +" When an error occurs after the last active :try/:endtry region has +" been left, termination behavior is as if no :try/:endtry has been +" seen. +"------------------------------------------------------------------------------- + +func Test_error_after_try_1() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + Xpath 'a' + try + Xpath 'b' + endtry + asdf + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_2() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_3() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_4() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + finally + Xpath 'b' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_6() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_7() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + finally + Xpath 'b' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 +" +" If a :try conditional stays inactive due to a preceding :continue, +" :break, :return, or :finish, its :finally clause should not be +" executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_loop_ctrl_statement() + let test =<< trim [CODE] + func F() + let loops = 2 + while loops > 0 + XloopNEXT + let loops = loops - 1 + try + if loops == 1 + Xloop 'a' + continue + call assert_report('should not get here') + elseif loops == 0 + Xloop 'b' + break + call assert_report('should not get here') + endif + + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xloop 'c' + endtry + call assert_report('should not get here') + endwhile + + try + Xpath 'd' + return + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'e' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'f' + call F() + Xpath 'g' + finish + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'h' + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('fa2c2b3c3degh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 +" +" If a :try conditional stays inactive due to a preceding error or +" interrupt or :throw, its :finally clause should not be executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_in_func() + let test =<< trim [CODE] + func Error() + try + Xpath 'b' + asdf " aborting error, triggering error exception + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endfunc + + Xpath 'a' + call Error() + call assert_report('should not get here') + + if 1 " not active due to error + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to error + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_interrupt() + let test =<< trim [CODE] + func Interrupt() + try + Xpath 'a' + call interrupt() " triggering interrupt exception + call assert_report('should not get here') + endtry + endfunc + + Xpath 'b' + try + call Interrupt() + catch /^Vim:Interrupt$/ + Xpath 'c' + finish + endtry + call assert_report('should not get here') + + if 1 " not active due to interrupt + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to interrupt + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('bac', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_throw() + let test =<< trim [CODE] + func Throw() + Xpath 'a' + throw 'xyz' + endfunc + + Xpath 'b' + call Throw() + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to :throw + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 23: :catch clauses for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" none of its :catch clauses should be executed. +"------------------------------------------------------------------------------- + +func Test_catch_after_throw() + let test =<< trim [CODE] + try + Xpath 'a' + throw "xyz" + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + catch /xyz/ + call assert_report('should not get here') + endtry + endif + catch /xyz/ + Xpath 'b' + endtry + + Xpath 'c' + throw "abc" + call assert_report('should not get here') + + try " not active due to :throw + call assert_report('should not get here') + catch /abc/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 24: :endtry for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" its :endtry should not rethrow the exception to the next surrounding +" active :try conditional. +"------------------------------------------------------------------------------- + +func Test_endtry_after_throw() + let test =<< trim [CODE] + try " try 1 + try " try 2 + Xpath 'a' + throw "xyz" " makes try 2 inactive + call assert_report('should not get here') + + try " try 3 + call assert_report('should not get here') + endtry " no rethrow to try 1 + catch /xyz/ " should catch although try 2 inactive + Xpath 'b' + endtry + catch /xyz/ " try 1 active, but exception already caught + call assert_report('should not get here') + endtry + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 27: Executing :finally clauses after :return {{{1 +" +" For a :return command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the called function is ended. +"------------------------------------------------------------------------------- + +func T27_F() + try + Xpath 'a' + try + Xpath 'b' + return + call assert_report('should not get here') + finally + Xpath 'c' + endtry + Xpath 'd' + finally + Xpath 'e' + endtry + call assert_report('should not get here') +endfunc + +func T27_G() + try + Xpath 'f' + return + call assert_report('should not get here') + finally + Xpath 'g' + call T27_F() + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func T27_H() + try + Xpath 'i' + call T27_G() + Xpath 'j' + finally + Xpath 'k' + return + call assert_report('should not get here') + endtry + call assert_report('should not get here') +endfunction + +func Test_finally_after_return() + XpathINIT + try + Xpath 'l' + call T27_H() + Xpath 'm' + finally + Xpath 'n' + endtry + call assert_equal('lifgabcehjkmn', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 28: Executing :finally clauses after :finish {{{1 +" +" For a :finish command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the sourced file is finished. +" +" This test executes the bodies of the functions F, G, and H from the +" previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func Test_finally_after_finish() + XpathINIT + + let scriptF = MakeScript("T27_F") + let scriptG = MakeScript("T27_G", scriptF) + let scriptH = MakeScript("T27_H", scriptG) + + try + Xpath 'A' + exec "source" scriptH + Xpath 'B' + finally + Xpath 'C' + endtry + Xpath 'D' + call assert_equal('AifgabcehjkBCD', g:Xpath) + call delete(scriptF) + call delete(scriptG) + call delete(scriptH) +endfunc + +"------------------------------------------------------------------------------- +" Test 29: Executing :finally clauses on errors {{{1 +" +" After an error in a command dynamically enclosed in a :try/:endtry +" region, :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_1() + let test =<< trim [CODE] + func F() + while 1 + try + Xpath 'a' + while 1 + try + Xpath 'b' + asdf " error + call assert_report('should not get here') + finally + Xpath 'c' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + finally + Xpath 'd' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + endfunc + + while 1 + try + Xpath 'e' + while 1 + call F() + call assert_report('should not get here') + break + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabcdf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_error_2() + let test =<< trim [CODE] + func G() abort + if 1 + try + Xpath 'a' + asdf " error + call assert_report('should not get here') + finally + Xpath 'b' + endtry | Xpath 'c' + endif | Xpath 'd' + call assert_report('should not get here') + endfunc + + if 1 + try + Xpath 'e' + call G() + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 30: Executing :finally clauses on interrupt {{{1 +" +" After an interrupt in a command dynamically enclosed in +" a :try/:endtry region, :finally clauses are executed and the +" script processing is terminated. +"------------------------------------------------------------------------------- + +func Test_finally_on_interrupt() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + call interrupt() + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + try + Xpath 'c' + try + Xpath 'd' + call interrupt() + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'o' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2no', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 31: Executing :finally clauses after :throw {{{1 +" +" After a :throw dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_throw_2() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + throw "exception" + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'c' + try + Xpath 'd' + throw "exception" + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + throw "exception" + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2n', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 34: :finally reason discarded by :continue {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :continue in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_after_continue() + let test =<< trim [CODE] + func C(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + continue " discards jump that caused the :finally + call assert_report('should not get here') + endtry + call assert_report('should not get here') + elseif loop == 2 + Xloop 'a' + endif + endwhile + endfunc + + call C("continue") + Xpath 'b' + call C("break") + Xpath 'c' + call C("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript C + unlet g:jump + Xpath 'e' + try + call C("error") + Xpath 'f' + finally + Xpath 'g' + try + call C("interrupt") + Xpath 'h' + finally + Xpath 'i' + call C("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 35: :finally reason discarded by :break {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :break in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_break() + let test =<< trim [CODE] + func B(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + break " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + Xloop 'a' + endfunc + + call B("continue") + Xpath 'b' + call B("break") + Xpath 'c' + call B("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript B + unlet g:jump + Xpath 'e' + try + call B("error") + Xpath 'f' + finally + Xpath 'g' + try + call B("interrupt") + Xpath 'h' + finally + Xpath 'i' + call B("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 36: :finally reason discarded by :return {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :return in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_return() + let test =<< trim [CODE] + func R(jump, retval) abort + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + return a:retval " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let sum = -R("continue", -8) + Xpath 'a' + let sum = sum - R("break", -16) + Xpath 'b' + let sum = sum - R("return", -32) + Xpath 'c' + try + let sum = sum - R("error", -64) + Xpath 'd' + finally + Xpath 'e' + try + let sum = sum - R("interrupt", -128) + Xpath 'f' + finally + Xpath 'g' + let sum = sum - R("throw", -256) + Xpath 'h' + endtry + endtry + Xpath 'i' + + let expected = 8 + 16 + 32 + 64 + 128 + 256 + call assert_equal(sum, expected) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 37: :finally reason discarded by :finish {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :finish in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_finish() + let test =<< trim [CODE] + func F(jump) " not executed as function, transformed to a script + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "finish" + finish + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + finish " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let scriptF = MakeScript("F") + delfunction F + + let g:jump = "continue" + exec "source" scriptF + Xpath 'a' + let g:jump = "break" + exec "source" scriptF + Xpath 'b' + let g:jump = "finish" + exec "source" scriptF + Xpath 'c' + try + let g:jump = "error" + exec "source" scriptF + Xpath 'd' + finally + Xpath 'e' + try + let g:jump = "interrupt" + exec "source" scriptF + Xpath 'f' + finally + Xpath 'g' + try + let g:jump = "throw" + exec "source" scriptF + Xpath 'h' + finally + Xpath 'i' + endtry + endtry + endtry + unlet g:jump + call delete(scriptF) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 38: :finally reason discarded by an error {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an error in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_error() + let test =<< trim [CODE] + func E(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + asdf " error; discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call E("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call E("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call E("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript E + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call E("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call E("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call E("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction E + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 39: :finally reason discarded by an interrupt {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an interrupt in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discarded_by_interrupt() + let test =<< trim [CODE] + func I(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + call interrupt() + let dummy = 0 + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + try + Xpath 'a' + call I("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call I("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call I("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript I + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call I("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call I("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call I("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction I + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'A' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghA', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 40: :finally reason discarded by :throw {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :throw in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_throw() + let test =<< trim [CODE] + func T(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + throw "xyz" " discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call T("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call T("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call T("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript T + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call T("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call T("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call T("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction T + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 49: Throwing exceptions across functions {{{1 +" +" When an exception is thrown but not caught inside a function, the +" caller is checked for a matching :catch clause. +"------------------------------------------------------------------------------- + +func T49_C() + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /arrgh/ + Xpath 'b' + endtry + Xpath 'c' +endfunc + +func T49_T1() + XloopNEXT + try + Xloop 'd' + throw "arrgh" + call assert_report('should not get here') + finally + Xloop 'e' + endtry + Xloop 'f' +endfunc + +func T49_T2() + try + Xpath 'g' + call T49_T1() + call assert_report('should not get here') + finally + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func Test_throw_exception_across_funcs() + XpathINIT + XloopINIT + try + Xpath 'i' + call T49_C() " throw and catch + Xpath 'j' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'k' + call T49_T1() " throw, one level + call assert_report('should not get here') + catch /arrgh/ + Xpath 'l' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'm' + call T49_T2() " throw, two levels + call assert_report('should not get here') + catch /arrgh/ + Xpath 'n' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'o' + + call assert_equal('iabcjkd2e2lmgd3e3hno', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 50: Throwing exceptions across script files {{{1 +" +" When an exception is thrown but not caught inside a script file, +" the sourcing script or function is checked for a matching :catch +" clause. +" +" This test executes the bodies of the functions C, T1, and T2 from +" the previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func T50_F() + try + Xpath 'A' + exec "source" g:scriptC + Xpath 'B' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'C' + exec "source" g:scriptT1 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'D' + catch /.*/ + call assert_report('should not get here') + endtry +endfunc + +func Test_throw_across_script() + XpathINIT + XloopINIT + let g:scriptC = MakeScript("T49_C") + let g:scriptT1 = MakeScript("T49_T1") + let scriptT2 = MakeScript("T49_T2", g:scriptT1) + + try + Xpath 'E' + call T50_F() + Xpath 'F' + exec "source" scriptT2 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'G' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'H' + call assert_equal('EAabcBCd2e2DFgd3e3hGH', g:Xpath) + + call delete(g:scriptC) + call delete(g:scriptT1) + call delete(scriptT2) + unlet g:scriptC g:scriptT1 scriptT2 +endfunc + +"------------------------------------------------------------------------------- +" Test 52: Uncaught exceptions {{{1 +" +" When an exception is thrown but not caught, an error message is +" displayed when the script is terminated. In case of an interrupt +" or error exception, the normal interrupt or error message(s) are +" displayed. +"------------------------------------------------------------------------------- + +func Test_uncaught_exception_1() + CheckEnglish + + let test =<< trim [CODE] + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: arrgh', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_2() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "oops" + call assert_report('should not get here')` + catch /arrgh/ + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: oops', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_3() + CheckEnglish + + let test =<< trim [CODE] + func T() + Xpath 'c' + throw "brrr" + call assert_report('should not get here')` + endfunc + + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + catch /.*/ + Xpath 'b' + call T() + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_4() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + finally + Xpath 'b' + throw "brrr" + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_5() + CheckEnglish + + " Need to catch and handle interrupt, otherwise the test will wait for the + " user to press <Enter> to continue + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_6() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + let x = novar " error E121; exception: E121 + catch /E15:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E121: Undefined variable: novar', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_7() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + " error E108/E488; exception: E488 + unlet novar # + catch /E108:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E488: Trailing characters: #', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 53: Nesting errors: :endif/:else/:elseif {{{1 +" +" For nesting errors of :if conditionals the correct error messages +" should be given. +"------------------------------------------------------------------------------- + +func Test_nested_if_else_errors() + CheckEnglish + + " :endif without :if + let code =<< trim END + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + while 1 + endif + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + finally + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + throw "a" + catch /a/ + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :else without :if + let code =<< trim END + else + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + while 1 + else + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + finally + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + throw "a" + catch /a/ + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :elseif without :if + let code =<< trim END + elseif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + while 1 + elseif + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + finally + elseif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + elseif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + throw "a" + catch /a/ + elseif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " multiple :else + let code =<< trim END + if 1 + else + else + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else') + + " :elseif after :else + let code =<< trim END + if 1 + else + elseif 1 + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E584: :elseif after :else') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 54: Nesting errors: :while/:endwhile {{{1 +" +" For nesting errors of :while conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_while_error() + CheckEnglish + + " :endwhile without :while + let code =<< trim END + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + if 1 + endwhile + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endif + let code =<< trim END + while 1 + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + finally + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endtry + let code =<< trim END + while 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endtry + let code =<< trim END + while 1 + if 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endif + let code =<< trim END + while 1 + try + finally + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + try + throw "a" + catch /a/ + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + throw "a" + catch /a/ + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 55: Nesting errors: :continue/:break {{{1 +" +" For nesting errors of :continue and :break commands the correct +" error messages should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_cont_break_error() + CheckEnglish + + " :continue without :while + let code =<< trim END + continue + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + if 1 + continue + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + finally + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + throw "a" + catch /a/ + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :break without :while + let code =<< trim END + break + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + if 1 + break + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + finally + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + throw "a" + catch /a/ + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 56: Nesting errors: :endtry {{{1 +" +" For nesting errors of :try conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_endtry_error() + CheckEnglish + + " :endtry without :try + let code =<< trim END + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + if 1 + endtry + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + while 1 + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " Missing :endif + let code =<< trim END + try + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + finally + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + finally + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + throw "a" + catch /a/ + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + throw "a" + catch /a/ + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 57: v:exception and v:throwpoint for user exceptions {{{1 +" +" v:exception evaluates to the value of the exception that was caught +" most recently and is not finished. (A caught exception is finished +" when the next ":catch", ":finally", or ":endtry" is reached.) +" v:throwpoint evaluates to the script/function name and line number +" where that exception has been thrown. +"------------------------------------------------------------------------------- + +func Test_user_exception_info() + CheckEnglish + + XpathINIT + XloopINIT + + func FuncException() + let g:exception = v:exception + endfunc + + func FuncThrowpoint() + let g:throwpoint = v:throwpoint + endfunc + + let scriptException = MakeScript("FuncException") + let scriptThrowPoint = MakeScript("FuncThrowpoint") + + command! CmdException let g:exception = v:exception + command! CmdThrowpoint let g:throwpoint = v:throwpoint + + func T(arg, line) + if a:line == 2 + throw a:arg " in line 2 + elseif a:line == 4 + throw a:arg " in line 4 + elseif a:line == 6 + throw a:arg " in line 6 + elseif a:line == 8 + throw a:arg " in line 8 + endif + endfunc + + func G(arg, line) + call T(a:arg, a:line) + endfunc + + func F(arg, line) + call G(a:arg, a:line) + endfunc + + let scriptT = MakeScript("T") + let scriptG = MakeScript("G", scriptT) + let scriptF = MakeScript("F", scriptG) + + try + Xpath 'a' + call F("oops", 2) + catch /.*/ + Xpath 'b' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "let exception = v:exception" + exec "let throwpoint = v:throwpoint" + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + CmdException + CmdThrowpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + call FuncException() + call FuncThrowpoint() + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "source" scriptException + exec "source" scriptThrowPoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + try + Xpath 'c' + call G("arrgh", 4) + catch /.*/ + Xpath 'd' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + + try + Xpath 'e' + let g:arg = "autsch" + let g:line = 6 + exec "source" scriptF + catch /.*/ + Xpath 'f' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("autsch", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<6\>', v:throwpoint) + finally + Xpath 'g' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + try + Xpath 'h' + let g:arg = "brrrr" + let g:line = 8 + exec "source" scriptG + catch /.*/ + Xpath 'i' + let exception = v:exception + let throwpoint = v:throwpoint + " Resolve scriptT for matching it against v:throwpoint. + call assert_equal("brrrr", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<8\>', v:throwpoint) + finally + Xpath 'j' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'k' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'l' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + finally + Xpath 'm' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + endtry + Xpath 'n' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + finally + Xpath 'o' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("", v:exception) + call assert_match('^$', v:throwpoint) + call assert_match('^$', v:throwpoint) + endtry + + call assert_equal('abcdefghijklmno', g:Xpath) + + unlet exception throwpoint + delfunction FuncException + delfunction FuncThrowpoint + call delete(scriptException) + call delete(scriptThrowPoint) + unlet scriptException scriptThrowPoint + delcommand CmdException + delcommand CmdThrowpoint + delfunction T + delfunction G + delfunction F + call delete(scriptT) + call delete(scriptG) + call delete(scriptF) + unlet scriptT scriptG scriptF +endfunc + +"------------------------------------------------------------------------------- +" +" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 +" +" v:exception and v:throwpoint work also for error and interrupt +" exceptions. +"------------------------------------------------------------------------------- + +func Test_execption_info_for_error() + CheckEnglish + + let test =<< trim [CODE] + func T(line) + if a:line == 2 + delfunction T " error (function in use) in line 2 + elseif a:line == 4 + call interrupt() + endif + endfunc + + while 1 + try + Xpath 'a' + call T(2) + call assert_report('should not get here') + catch /.*/ + Xpath 'b' + if v:exception !~ 'Vim(delfunction):' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<T\>' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<2\>' + call assert_report('should not get here') + endif + finally + Xpath 'c' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'd' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + + while 1 + try + Xpath 'e' + call T(4) + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + if v:exception != 'Vim:Interrupt' + call assert_report('should not get here') + endif + if v:throwpoint !~ 'function T' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<4\>' + call assert_report('should not get here') + endif + finally + Xpath 'g' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'h' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 +" +" When a :catch clause is left by a ":break" etc or an error or +" interrupt exception, v:exception and v:throwpoint are reset. They +" are not affected by an exception that is discarded before being +" caught. +"------------------------------------------------------------------------------- +func Test_exception_info_on_discard() + CheckEnglish + + let test =<< trim [CODE] + let sfile = expand("<sfile>") + + while 1 + try + throw "x1" + catch /.*/ + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + throw "x2" + catch /.*/ + break + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + break + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x3" + catch /.*/ + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'a' + + while 1 + try + let intcaught = 0 + try + try + throw "x4" + catch /.*/ + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'b' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x5" + " missing endif + catch /.*/ + call assert_report('should not get here') + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(catch):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'c' + + try + while 1 + try + throw "x6" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + try + while 1 + try + throw "x7" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x8" + finally + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'd' + + while 1 + try + let intcaught = 0 + try + try + throw "x9" + finally + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'e' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x10" + " missing endif + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(finally):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'f' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x11" + " missing endif + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(endtry):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 60: (Re)throwing v:exception; :echoerr. {{{1 +" +" A user exception can be rethrown after catching by throwing +" v:exception. An error or interrupt exception cannot be rethrown +" because Vim exceptions cannot be faked. A Vim exception using the +" value of v:exception can, however, be triggered by the :echoerr +" command. +"------------------------------------------------------------------------------- + +func Test_rethrow_exception_1() + XpathINIT + try + try + Xpath 'a' + throw "oops" + catch /oops/ + Xpath 'b' + throw v:exception " rethrow user exception + catch /.*/ + call assert_report('should not get here') + endtry + catch /^oops$/ " catches rethrown user exception + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_2() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e + call assert_report('should not get here') + catch /^Vim(write):/ + let caught = 1 + throw v:exception " throw error: cannot fake Vim exception + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(throw):/ " catches throw error + let caught = caught + 1 + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + asdf + catch /^Vim/ " catch error exception + let caught = 1 + " Trigger Vim error exception with value specified after :echoerr + let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + echoerr value + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(echoerr):/ + let caught = caught + 1 + call assert_match(value, v:exception) + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let errcaught = 0 + try + Xpath 'a' + let intcaught = 0 + call interrupt() + catch /^Vim:/ " catch interrupt exception + let intcaught = 1 + " Trigger Vim error exception with value specified after :echoerr + echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, intcaught) + endtry + catch /^Vim(echoerr):/ + let errcaught = 1 + call assert_match('Interrupt', v:exception) + finally + Xpath 'c' + call assert_equal(1, errcaught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 61: Catching interrupt exceptions {{{1 +" +" When an interrupt occurs inside a :try/:endtry region, an +" interrupt exception is thrown and can be caught. Its value is +" "Vim:Interrupt". If the interrupt occurs after an error or a :throw +" but before a matching :catch is reached, all following :catches of +" that try block are ignored, but the interrupt exception can be +" caught by the next surrounding try conditional. An interrupt is +" ignored when there is a previous interrupt that has not been caught +" or causes a :finally clause to be executed. +"------------------------------------------------------------------------------- + +func Test_catch_intr_exception() + let test =<< trim [CODE] + while 1 + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + finally + Xpath 'c' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'e' + asdf + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + call interrupt() + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'h' + finally + Xpath 'i' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'k' + throw "x" + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /x/ + Xpath 'l' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'm' + finally + Xpath 'n' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'o' + break + endtry + endwhile + + while 1 + try + try + Xpath 'p' + call interrupt() + call assert_report('should not get here') + catch /do_not_catch/ + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'q' + finally + Xpath 'r' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 's' + break + endtry + endwhile + + Xpath 't' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopqrst', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 62: Catching error exceptions {{{1 +" +" An error inside a :try/:endtry region is converted to an exception +" and can be caught. The error exception has a "Vim(cmdname):" prefix +" where cmdname is the name of the failing command, or a "Vim:" prefix +" if no command name is known. The "Vim" prefixes cannot be faked. +"------------------------------------------------------------------------------- + +func Test_catch_err_exception_1() + XpathINIT + while 1 + try + try + let caught = 0 + unlet novar + catch /^Vim(unlet):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E108: No such variable: "novar"', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_2() + XpathINIT + while 1 + try + try + let caught = 0 + throw novar " error in :throw + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E121: Undefined variable: novar', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_3() + XpathINIT + while 1 + try + try + let caught = 0 + throw "Vim:faked" " error: cannot fake Vim exception + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E608: Cannot :throw exceptions with 'Vim' prefix", + \ v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_4() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + call F() + catch /^Vim(endfunction):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_5() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + ExecAsScript F + catch /^Vim:/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_6() + XpathINIT + func G() + call G() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call G() + catch /^Vim(call):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc G +endfunc + +func Test_catch_err_exception_7() + XpathINIT + func H() + return H() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call H() + catch /^Vim(return):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break " discard error for $VIMNOERRTHROW + endtry + call assert_report('should not get here') + endwhile + + call assert_equal('abc', g:Xpath) + delfunc H +endfunc + +"------------------------------------------------------------------------------- +" Test 63: Suppressing error exceptions by :silent!. {{{1 +" +" A :silent! command inside a :try/:endtry region suppresses the +" conversion of errors to an exception and the immediate abortion on +" error. When the commands executed by the :silent! themselves open +" a new :try/:endtry region, conversion of errors to exception and +" immediate abortion is switched on again - until the next :silent! +" etc. The :silent! has the effect of setting v:errmsg to the error +" message text (without displaying it) and continuing with the next +" script line. +" +" When a command triggering autocommands is executed by :silent! +" inside a :try/:endtry, the autocommand execution is not suppressed +" on error. +" +" This test reuses the function MSG() from the previous test. +"------------------------------------------------------------------------------- + +func Test_silent_exception() + XpathINIT + XloopINIT + let g:taken = "" + + func S(n) abort + XloopNEXT + let g:taken = g:taken . "E" . a:n + let v:errmsg = "" + exec "asdf" . a:n + + " Check that ":silent!" continues: + Xloop 'a' + + " Check that ":silent!" sets "v:errmsg": + call assert_match("E492: Not an editor command", v:errmsg) + endfunc + + func Foo() + while 1 + try + try + let caught = 0 + " This is not silent: + call S(3) + catch /^Vim:/ + Xpath 'b' + let caught = 1 + let errmsg3 = substitute(v:exception, '^Vim:', '', "") + silent! call S(4) + finally + call assert_equal(1, caught) + Xpath 'c' + call assert_match("E492: Not an editor command", errmsg3) + silent! call S(5) + " Break out of try conditionals that cover ":silent!". This also + " discards the aborting error when $VIMNOERRTHROW is non-zero. + break + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endwhile + " This is a double ":silent!" (see caller). + silent! call S(6) + endfunc + + func Bar() + try + silent! call S(2) + silent! execute "call Foo() | call S(7)" + silent! call S(8) + endtry " normal end of try cond that covers ":silent!" + " This has a ":silent!" from the caller: + call S(9) + endfunc + + silent! call S(1) + silent! call Bar() + silent! call S(10) + + call assert_equal("E1E2E3E4E5E6E7E8E9E10", g:taken) + + augroup TMP + au! + autocmd BufWritePost * Xpath 'd' + augroup END + + Xpath 'e' + silent! write /i/m/p/o/s/s/i/b/l/e + Xpath 'f' + + call assert_equal('a2a3ba5ca6a7a8a9a10a11edf', g:Xpath) + + augroup TMP + au! + augroup END + augroup! TMP + delfunction S + delfunction Foo + delfunction Bar +endfunc + +"------------------------------------------------------------------------------- +" Test 64: Error exceptions after error, interrupt or :throw {{{1 +" +" When an error occurs after an interrupt or a :throw but before +" a matching :catch is reached, all following :catches of that try +" block are ignored, but the error exception can be caught by the next +" surrounding try conditional. Any previous error exception is +" discarded. An error is ignored when there is a previous error that +" has not been caught. +"------------------------------------------------------------------------------- + +func Test_exception_after_error_1() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + while 1 + if 1 + " Missing :endif + endwhile " throw error exception + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_2() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + try + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_3() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + call interrupt() + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_4() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + throw "x" + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /x/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_5() + XpathINIT + while 1 + try + try + let caught = 0 + Xpath 'a' + endif " :endif without :if; throw error exception + if 1 + " Missing :endif + catch /do_not_catch/ " ignore new error + call assert_report('should not get here') + catch /^Vim(endif):/ + Xpath 'b' + let caught = 1 + catch /^Vim(/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 65: Errors in the /pattern/ argument of a :catch {{{1 +" +" On an error in the /pattern/ argument of a :catch, the :catch does +" not match. Any following :catches of the same :try/:endtry don't +" match either. Finally clauses are executed. +"------------------------------------------------------------------------------- + +func Test_catch_pattern_error() + CheckEnglish + XpathINIT + + try + try + Xpath 'a' + throw "oops" + catch /^oops$/ + Xpath 'b' + catch /\)/ " not checked; exception has already been caught + call assert_report('should not get here') + endtry + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) + + XpathINIT + func F() + try + try + try + Xpath 'a' + throw "ab" + catch /abc/ " does not catch + call assert_report('should not get here') + catch /\)/ " error; discards exception + call assert_report('should not get here') + catch /.*/ " not checked + call assert_report('should not get here') + finally + Xpath 'b' + endtry + call assert_report('should not get here') + catch /^ab$/ " checked, but original exception is discarded + call assert_report('should not get here') + catch /^Vim(catch):/ + Xpath 'c' + call assert_match('Vim(catch):E475: Invalid argument:', v:exception) + finally + Xpath 'd' + endtry + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + endfunc + + call F() + call assert_equal('abcdef', g:Xpath) + + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 66: Stop range :call on error, interrupt, or :throw {{{1 +" +" When a function which is multiply called for a range since it +" doesn't handle the range itself has an error in a command +" dynamically enclosed by :try/:endtry or gets an interrupt or +" executes a :throw, no more calls for the remaining lines in the +" range are made. On an error in a command not dynamically enclosed +" by :try/:endtry, the function is executed again for the remaining +" lines in the range. +"------------------------------------------------------------------------------- + +func Test_stop_range_on_error() + let test =<< trim [CODE] + let file = tempname() + exec "edit" file + call setline(1, ['line 1', 'line 2', 'line 3']) + let taken = "" + let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" + + func F(reason, n) abort + let g:taken = g:taken .. "F" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") .. + \ "(" .. line(".") .. ")" + + if a:reason == "error" + asdf + elseif a:reason == "interrupt" + call interrupt() + elseif a:reason == "throw" + throw "xyz" + elseif a:reason == "aborting error" + XloopNEXT + call assert_equal(g:taken, g:expected) + try + bwipeout! + call delete(g:file) + asdf + endtry + endif + endfunc + + func G(reason, n) + let g:taken = g:taken .. "G" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") + 1,3call F(a:reason, a:n) + endfunc + + Xpath 'a' + call G("error", 1) + try + Xpath 'b' + try + call G("error", 2) + call assert_report('should not get here') + finally + Xpath 'c' + try + call G("interrupt", 3) + call assert_report('should not get here') + finally + Xpath 'd' + try + call G("throw", 4) + call assert_report('should not get here') + endtry + endtry + endtry + catch /xyz/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + call G("aborting error", 5) + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 67: :throw across :call command {{{1 +" +" On a call command, an exception might be thrown when evaluating the +" function name, during evaluation of the arguments, or when the +" function is being executed. The exception can be caught by the +" caller. +"------------------------------------------------------------------------------- + +func THROW(x, n) + if a:n == 1 + Xpath 'A' + elseif a:n == 2 + Xpath 'B' + elseif a:n == 3 + Xpath 'C' + endif + throw a:x +endfunc + +func NAME(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + Xpath 'D' + elseif a:n == 3 + Xpath 'E' + elseif a:n == 4 + Xpath 'F' + endif + return a:x +endfunc + +func ARG(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + call assert_report('should not get here') + elseif a:n == 3 + Xpath 'G' + elseif a:n == 4 + Xpath 'I' + endif + return a:x +endfunc + +func Test_throw_across_call_cmd() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + endfunc + + while 1 + try + let v:errmsg = "" + + while 1 + try + Xpath 'b' + call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'd' + call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'f' + call {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'h' + call {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunction F +endfunc + +"------------------------------------------------------------------------------- +" Test 68: :throw across function calls in expressions {{{1 +" +" On a function call within an expression, an exception might be +" thrown when evaluating the function name, during evaluation of the +" arguments, or when the function is being executed. The exception +" can be caught by the caller. +" +" This test reuses the functions THROW(), NAME(), and ARG() from the +" previous test. +"------------------------------------------------------------------------------- + +func Test_throw_across_call_expr() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + return a:x + endfunction + + while 1 + try + let error = 0 + let v:errmsg = "" + + while 1 + try + Xpath 'b' + let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var1')) + + while 1 + try + Xpath 'd' + let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var2')) + + while 1 + try + Xpath 'f' + let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var3')) + + while 1 + try + Xpath 'h' + let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(exists('var4') && var4 == 4711) + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 +" +" When a function call made during expression evaluation is aborted +" due to an error inside a :try/:endtry region or due to an interrupt +" or a :throw, the expression evaluation is aborted as well. No +" message is displayed for the cancelled expression evaluation. On an +" error not inside :try/:endtry, the expression evaluation continues. +"------------------------------------------------------------------------------- + +func Test_expr_eval_error() + let test =<< trim [CODE] + let taken = "" + + func ERR(n) + let g:taken = g:taken .. "E" .. a:n + asdf + endfunc + + func ERRabort(n) abort + let g:taken = g:taken .. "A" .. a:n + asdf + endfunc " returns -1; may cause follow-up msg for illegal var/func name + + func WRAP(n, arg) + let g:taken = g:taken .. "W" .. a:n + let g:saved_errmsg = v:errmsg + return arg + endfunc + + func INT(n) + let g:taken = g:taken .. "I" .. a:n + call interrupt() + endfunc + + func THR(n) + let g:taken = g:taken .. "T" .. a:n + throw "should not be caught" + endfunc + + func CONT(n) + let g:taken = g:taken .. "C" .. a:n + endfunc + + func MSG(n) + let g:taken = g:taken .. "M" .. a:n + let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg + let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" + call assert_match(msgptn, errmsg) + let v:errmsg = "" + let g:saved_errmsg = "" + endfunc + + let v:errmsg = "" + + try + let t = 1 + while t <= 9 + Xloop 'a' + try + if t == 1 + let v{ERR(t) + CONT(t)} = 0 + elseif t == 2 + let v{ERR(t) + CONT(t)} + elseif t == 3 + let var = exists('v{ERR(t) + CONT(t)}') + elseif t == 4 + unlet v{ERR(t) + CONT(t)} + elseif t == 5 + function F{ERR(t) + CONT(t)}() + endfunction + elseif t == 6 + function F{ERR(t) + CONT(t)} + elseif t == 7 + let var = exists('*F{ERR(t) + CONT(t)}') + elseif t == 8 + delfunction F{ERR(t) + CONT(t)} + elseif t == 9 + let var = ERR(t) + CONT(t) + endif + catch /asdf/ + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been thrown after the original error. + let v:errmsg = "" + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 10 + while t <= 18 + Xloop 'b' + try + if t == 10 + let v{INT(t) + CONT(t)} = 0 + elseif t == 11 + let v{INT(t) + CONT(t)} + elseif t == 12 + let var = exists('v{INT(t) + CONT(t)}') + elseif t == 13 + unlet v{INT(t) + CONT(t)} + elseif t == 14 + function F{INT(t) + CONT(t)}() + endfunction + elseif t == 15 + function F{INT(t) + CONT(t)} + elseif t == 16 + let var = exists('*F{INT(t) + CONT(t)}') + elseif t == 17 + delfunction F{INT(t) + CONT(t)} + elseif t == 18 + let var = INT(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ + " An error exception has been triggered after the interrupt. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard interrupt + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 19 + while t <= 27 + Xloop 'c' + try + if t == 19 + let v{THR(t) + CONT(t)} = 0 + elseif t == 20 + let v{THR(t) + CONT(t)} + elseif t == 21 + let var = exists('v{THR(t) + CONT(t)}') + elseif t == 22 + unlet v{THR(t) + CONT(t)} + elseif t == 23 + function F{THR(t) + CONT(t)}() + endfunction + elseif t == 24 + function F{THR(t) + CONT(t)} + elseif t == 25 + let var = exists('*F{THR(t) + CONT(t)}') + elseif t == 26 + delfunction F{THR(t) + CONT(t)} + elseif t == 27 + let var = THR(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been triggered after the :throw. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard exception + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + let v{ERR(28) + CONT(28)} = 0 + call MSG(28) + let v{ERR(29) + CONT(29)} + call MSG(29) + let var = exists('v{ERR(30) + CONT(30)}') + call MSG(30) + unlet v{ERR(31) + CONT(31)} + call MSG(31) + function F{ERR(32) + CONT(32)}() + endfunction + call MSG(32) + function F{ERR(33) + CONT(33)} + call MSG(33) + let var = exists('*F{ERR(34) + CONT(34)}') + call MSG(34) + delfunction F{ERR(35) + CONT(35)} + call MSG(35) + let var = ERR(36) + CONT(36) + call MSG(36) + + let saved_errmsg = "" + + let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 + call MSG(37) + let v{WRAP(38, ERRabort(38)) + CONT(38)} + call MSG(38) + let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') + call MSG(39) + unlet v{WRAP(40, ERRabort(40)) + CONT(40)} + call MSG(40) + function F{WRAP(41, ERRabort(41)) + CONT(41)}() + endfunction + call MSG(41) + function F{WRAP(42, ERRabort(42)) + CONT(42)} + call MSG(42) + let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') + call MSG(43) + delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} + call MSG(44) + let var = ERRabort(45) + CONT(45) + call MSG(45) + Xpath 'd' + + let expected = "" + \ .. "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" + \ .. "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" + \ .. "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" + \ .. "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" + \ .. "E34C34M34E35C35M35E36C36M36" + \ .. "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" + \ .. "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1a2a3a4a5a6a7a8a9" + \ .. "b10b11b12b13b14b15b16b17b18" + \ .. "c19c20c21c22c23c24c25c26c27d" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 +" +" When a function call made during evaluation of an expression in +" braces as part of a function name after ":function" is aborted due +" to an error inside a :try/:endtry region or due to an interrupt or +" a :throw, the expression evaluation is aborted as well, and the +" function definition is ignored, skipping all commands to the +" ":endfunction". On an error not inside :try/:endtry, the expression +" evaluation continues and the function gets defined, and can be +" called and deleted. "------------------------------------------------------------------------------- +func Test_brace_expr_error() + let test =<< trim [CODE] + func ERR() abort + Xloop 'a' + asdf + endfunc " returns -1 + + func OK() + Xloop 'b' + let v:errmsg = "" + return 0 + endfunc + + let v:errmsg = "" + + Xpath 'c' + func F{1 + ERR() + OK()}(arg) + " F0 should be defined. + if exists("a:arg") && a:arg == "calling" + Xpath 'd' + else + call assert_report('should not get here') + endif + endfunction + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'e' + call F{1 + ERR() + OK()}("calling") + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'f' + delfunction F{1 + ERR() + OK()} + call assert_equal("", v:errmsg) + XloopNEXT + + try + while 1 + try + Xpath 'g' + func G{1 + ERR() + OK()}(arg) + " G0 should not be defined, and the function body should be + " skipped. + call assert_report('should not get here') + " Use an unmatched ":finally" to check whether the body is + " skipped when an error occurs in ERR(). This works whether or + " not the exception is converted to an exception. + finally + call assert_report('should not get here') + endtry + try + call assert_report('should not get here') + endfunction + + call assert_report('should not get here') + catch /asdf/ + " Jumped to when the function is not defined and the body is + " skipped. + Xpath 'h' + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'i' + break + endtry " jumped to when the body is not skipped + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ca1b1ea2b2dfa3b3ga4hi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 78: Messages on parsing errors in expression evaluation {{{1 +" +" When an expression evaluation detects a parsing error, an error +" message is given and converted to an exception, and the expression +" evaluation is aborted. +"------------------------------------------------------------------------------- +func Test_expr_eval_error_msg() + CheckEnglish + + let test =<< trim [CODE] + let taken = "" + + func F(n) + let g:taken = g:taken . "F" . a:n + endfunc + + func MSG(n, enr, emsg) + let g:taken = g:taken . "M" . a:n + call assert_match('^' .. a:enr .. ':', v:errmsg) + call assert_match(a:emsg, v:errmsg) + endfunc + + func CONT(n) + let g:taken = g:taken . "C" . a:n + endfunc + + let v:errmsg = "" + try + let t = 1 + while t <= 14 + let g:taken = g:taken . "T" . t + let v:errmsg = "" + try + if t == 1 + let v{novar + CONT(t)} = 0 + elseif t == 2 + let v{novar + CONT(t)} + elseif t == 3 + let var = exists('v{novar + CONT(t)}') + elseif t == 4 + unlet v{novar + CONT(t)} + elseif t == 5 + function F{novar + CONT(t)}() + endfunction + elseif t == 6 + function F{novar + CONT(t)} + elseif t == 7 + let var = exists('*F{novar + CONT(t)}') + elseif t == 8 + delfunction F{novar + CONT(t)} + elseif t == 9 + echo novar + CONT(t) + elseif t == 10 + echo v{novar + CONT(t)} + elseif t == 11 + echo F{novar + CONT(t)} + elseif t == 12 + let var = novar + CONT(t) + elseif t == 13 + let var = v{novar + CONT(t)} + elseif t == 14 + let var = F{novar + CONT(t)}() + endif + catch /^Vim\((\a\+)\)\=:/ + Xloop 'a' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'b' + if t <= 8 && t != 3 && t != 7 + call MSG(t, 'E475', 'Invalid argument\>') + else + call MSG(t, 'E121', "Undefined variable") + endif + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + func T(n, expr, enr, emsg) + try + let g:taken = g:taken . "T" . a:n + let v:errmsg = "" + try + execute "let var = " . a:expr + catch /^Vim\((\a\+)\)\=:/ + Xloop 'c' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'd' + call MSG(a:n, a:enr, a:emsg) + XloopNEXT + " Discard an aborting error: + return + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endfunc + + call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") + call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") + call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") + call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") + call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") + call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") + call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") + call T(22, '1 2 + CONT(22)', 'E15', "Invalid expression") + call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") + call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") + call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") + call T(26, '& + CONT(26)', 'E112', "Option name missing") + call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") + + let expected = "" + \ .. "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" + \ .. "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" + \ .. "T26M26T27M27" + + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1b1a2b2a3b3a4b4a5b5a6b6a7b7a8b8a9b9a10b10a11b11a12b12" + \ .. "a13b13a14b14c15d15c16d16c17d17c18d18c19d19c20d20" + \ .. "c21d21c22d22c23d23c24d24c25d25c26d26c27d27" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 79: Throwing one of several errors for the same command {{{1 +" +" When several errors appear in a row (for instance during expression +" evaluation), the first as the most specific one is used when +" throwing an error exception. If, however, a syntax error is +" detected afterwards, this one is used for the error exception. +" On a syntax error, the next command is not executed, on a normal +" error, however, it is (relevant only in a function without the +" "abort" flag). v:errmsg is not set. +" +" If throwing error exceptions is configured off, v:errmsg is always +" set to the latest error message, that is, to the more general +" message or the syntax error, respectively. +"------------------------------------------------------------------------------- +func Test_throw_multi_error() + CheckEnglish + + let test =<< trim [CODE] + func NEXT(cmd) + exec a:cmd . " | Xloop 'a'" + endfun + + call NEXT('echo novar') " (checks nextcmd) + XloopNEXT + call NEXT('let novar #') " (skips nextcmd) + XloopNEXT + call NEXT('unlet novar #') " (skips nextcmd) + XloopNEXT + call NEXT('let {novar}') " (skips nextcmd) + XloopNEXT + call NEXT('unlet{ novar}') " (skips nextcmd) + + call assert_equal('a1', g:Xpath) + XpathINIT + XloopINIT + + func EXEC(cmd) + exec a:cmd + endfunc + + try + while 1 " dummy loop + try + let v:errmsg = "" + call EXEC('echo novar') " normal error + catch /^Vim\((\a\+)\)\=:/ + Xpath 'b' + call assert_match('E121: Undefined variable: novar', v:exception) + finally + Xpath 'c' + call assert_equal("", v:errmsg) + break + endtry + endwhile + + Xpath 'd' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' novar #') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'e' + call assert_match('E488: Trailing characters', v:exception) + finally + Xloop 'f' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + + Xpath 'g' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' {novar}') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'h' + call assert_match('E475: Invalid argument: {novar}', v:exception) + finally + Xloop 'i' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'j' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bcde1f1e2f2gh3i3h4i4j', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 80: Syntax error in expression for illegal :elseif {{{1 +" +" If there is a syntax error in the expression after an illegal +" :elseif, an error message is given (or an error exception thrown) +" for the illegal :elseif rather than the expression error. +"------------------------------------------------------------------------------- +func Test_if_syntax_error() + CheckEnglish + + let test =<< trim [CODE] + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + Xpath 'a' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + Xpath 'b' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + elseif 1 ||| 2 + Xpath 'c' + call assert_match('E582: :elseif without :if', v:errmsg) + + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + Xpath 'd' + call assert_match('E582: :elseif without :if', v:errmsg) + + while 1 + try + try + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'e' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'f' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'h' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'i' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + elseif 1 ||| 2 + catch /^Vim\((\a\+)\)\=:/ + Xpath 'k' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'l' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'm' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + catch /^Vim\((\a\+)\)\=:/ + Xpath 'n' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'o' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'p' + break + endtry + endwhile + Xpath 'q' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopq', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 81: Discarding exceptions after an error or interrupt {{{1 +" +" When an exception is thrown from inside a :try conditional without +" :catch and :finally clauses and an error or interrupt occurs before +" the :endtry is reached, the exception is discarded. +"------------------------------------------------------------------------------- + +func Test_discard_exception_after_error_1() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" TODO: Not able inject an interrupt after throwing an exception +func Disable_Test_discard_exception_after_error_2() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call interrupt() " FIXME: throw is not interrupted here + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + "------------------------------------------------------------------------------- " Test 87 using (expr) ? funcref : funcref {{{1 " @@ -1443,7 +6241,7 @@ endfunc " Undefined behavior was detected by ubsan with line continuation " after an empty line. "------------------------------------------------------------------------------- -func Test_script_emty_line_continuation() +func Test_script_empty_line_continuation() \ endfunc @@ -1495,55 +6293,6 @@ func Test_bitwise_functions() call assert_fails("call invert({})", 'E728:') endfunc -" Test trailing text after :endfunction {{{1 -func Test_endfunction_trailing() - call assert_false(exists('*Xtest')) - - exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - " trailing line break - exe "func Xtest()\necho 'hello'\nendfunc\n" - call assert_true(exists('*Xtest')) - delfunc Xtest - - set verbose=1 - exe "func Xtest()\necho 'hello'\nendfunc \" garbage" - call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - - exe "func Xtest()\necho 'hello'\nendfunc garbage" - call assert_match('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - set verbose=0 - - function Foo() - echo 'hello' - endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' - delfunc Foo -endfunc - -func Test_delfunction_force() - delfunc! Xtest - delfunc! Xtest - func Xtest() - echo 'nothing' - endfunc - delfunc! Xtest - delfunc! Xtest -endfunc - " Test using bang after user command {{{1 func Test_user_command_with_bang() command -bang Nieuw let nieuw = 1 @@ -1553,26 +6302,6 @@ func Test_user_command_with_bang() delcommand Nieuw endfunc -" Test for script-local function -func <SID>DoLast() - call append(line('$'), "last line") -endfunc - -func s:DoNothing() - call append(line('$'), "nothing line") -endfunc - -func Test_script_local_func() - set nocp nomore viminfo+=nviminfo - new - nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> - - normal _x - call assert_equal('nothing line', getline(2)) - call assert_equal('last line', getline(3)) - enew! | close -endfunc - func Test_script_expand_sfile() let lines =<< trim END func s:snr() @@ -1653,6 +6382,22 @@ func Test_compound_assignment_operators() call assert_equal(4.2, x) call assert_fails('let x %= 0.5', 'E734') call assert_fails('let x .= "f"', 'E734') + let x = !3.14 + call assert_equal(0.0, x) + + " integer and float operations + let x = 1 + let x *= 2.1 + call assert_equal(2.1, x) + let x = 1 + let x /= 0.25 + call assert_equal(4.0, x) + let x = 1 + call assert_fails('let x %= 0.25', 'E734:') + let x = 1 + call assert_fails('let x .= 0.25', 'E734:') + let x = 1.0 + call assert_fails('let x += [1.1]', 'E734:') endif " Test for environment variable @@ -1716,87 +6461,6 @@ func Test_unlet_env() call assert_equal('', $TESTVAR) endfunc -func Test_funccall_garbage_collect() - func Func(x, ...) - call add(a:x, a:000) - endfunc - call Func([], []) - " Must not crash cause by invalid freeing - call test_garbagecollect_now() - call assert_true(v:true) - delfunc Func -endfunc - -func Test_function_defined_line() - if has('gui_running') - " Can't catch the output of gvim. - return - endif - - let lines =<< trim [CODE] - " F1 - func F1() - " F2 - func F2() - " - " - " - return - endfunc - " F3 - execute "func F3()\n\n\n\nreturn\nendfunc" - " F4 - execute "func F4()\n - \\n - \\n - \\n - \return\n - \endfunc" - endfunc - " F5 - execute "func F5()\n\n\n\nreturn\nendfunc" - " F6 - execute "func F6()\n - \\n - \\n - \\n - \return\n - \endfunc" - call F1() - verbose func F1 - verbose func F2 - verbose func F3 - verbose func F4 - verbose func F5 - verbose func F6 - qall! - [CODE] - - call writefile(lines, 'Xtest.vim') - let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') - call assert_equal(0, v:shell_error) - - let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') - call assert_match(' line 2$', m) - - let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') - call assert_match(' line 4$', m) - - let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') - call assert_match(' line 11$', m) - - let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') - call assert_match(' line 13$', m) - - let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') - call assert_match(' line 21$', m) - - let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') - call assert_match(' line 23$', m) - - call delete('Xtest.vim') -endfunc - " Test for missing :endif, :endfor, :endwhile and :endtry {{{1 func Test_missing_end() call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') @@ -1834,13 +6498,14 @@ func Test_missing_end() " Missing 'in' in a :for statement call assert_fails('for i range(1) | endfor', 'E690:') + + " Incorrect number of variables in for + call assert_fails('for [i,] in range(3) | endfor', 'E475:') endfunc " Test for deep nesting of if/for/while/try statements {{{1 func Test_deep_nest() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let lines =<< trim [SCRIPT] " Deep nesting of if ... endif @@ -1929,14 +6594,113 @@ func Test_deep_nest() call delete('Xscript') endfunc -" Test for <sfile>, <slnum> in a function {{{1 -func Test_sfile_in_function() - func Xfunc() - call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) - call assert_equal('2', expand('<slnum>')) - endfunc - call Xfunc() - delfunc Xfunc +" Test for errors in converting to float from various types {{{1 +func Test_float_conversion_errors() + if has('float') + call assert_fails('let x = 4.0 % 2.0', 'E804') + call assert_fails('echo 1.1[0]', 'E806') + call assert_fails('echo sort([function("min"), 1], "f")', 'E891:') + call assert_fails('echo 3.2 == "vim"', 'E892:') + call assert_fails('echo sort([[], 1], "f")', 'E893:') + call assert_fails('echo sort([{}, 1], "f")', 'E894:') + call assert_fails('echo 3.2 == v:true', 'E362:') + " call assert_fails('echo 3.2 == v:none', 'E907:') + endif +endfunc + +" invalid function names {{{1 +func Test_invalid_function_names() + " function name not starting with capital + let caught_e128 = 0 + try + func! g:test() + echo "test" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name includes a colon + let caught_e884 = 0 + try + func! b:test() + echo "test" + endfunc + catch /E884:/ + let caught_e884 = 1 + endtry + call assert_equal(1, caught_e884) + + " function name folowed by # + let caught_e128 = 0 + try + func! test2() "# + echo "test2" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name starting with/without "g:", buffer-local funcref. + function! g:Foo(n) + return 'called Foo(' . a:n . ')' + endfunction + let b:my_func = function('Foo') + call assert_equal('called Foo(1)', b:my_func(1)) + call assert_equal('called Foo(2)', g:Foo(2)) + call assert_equal('called Foo(3)', Foo(3)) + delfunc g:Foo + + " script-local function used in Funcref must exist. + let lines =<< trim END + func s:Testje() + return "foo" + endfunc + let Bar = function('s:Testje') + call assert_equal(0, exists('s:Testje')) + call assert_equal(1, exists('*s:Testje')) + call assert_equal(1, exists('Bar')) + call assert_equal(1, exists('*Bar')) + END + call writefile(lines, 'Xscript') + source Xscript + call delete('Xscript') +endfunc + +" substring and variable name {{{1 +func Test_substring_var() + let str = 'abcdef' + let n = 3 + call assert_equal('def', str[n:]) + call assert_equal('abcd', str[:n]) + call assert_equal('d', str[n:n]) + unlet n + let nn = 3 + call assert_equal('def', str[nn:]) + call assert_equal('abcd', str[:nn]) + call assert_equal('d', str[nn:nn]) + unlet nn + let b:nn = 4 + call assert_equal('ef', str[b:nn:]) + call assert_equal('abcde', str[:b:nn]) + call assert_equal('e', str[b:nn:b:nn]) + unlet b:nn +endfunc + +" Test using s: with a typed command {{{1 +func Test_typed_script_var() + CheckRunVimInTerminal + + let buf = RunVimInTerminal('', {'rows': 6}) + + " Deep nesting of if ... endif + call term_sendkeys(buf, ":echo get(s:, 'foo', 'x')\n") + call TermWait(buf) + call WaitForAssert({-> assert_match('^E116:', term_getline(buf, 5))}) + + call StopVimInTerminal(buf) endfunc func Test_for_over_string() diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index e712896562..2bf8c3fc77 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -80,6 +80,10 @@ func Test_edit_change() call setline(1, "\t⒌") normal Cx call assert_equal('x', getline(1)) + " Do a visual block change + call setline(1, ['a', 'b', 'c']) + exe "normal gg3l\<C-V>2jcx" + call assert_equal(['a x', 'b x', 'c x'], getline(1, '$')) bwipe! set virtualedit= endfunc @@ -289,6 +293,16 @@ func Test_replace_after_eol() call append(0, '"r"') normal gg$5lrxa call assert_equal('"r" x', getline(1)) + " visual block replace + %d _ + call setline(1, ['a', '', 'b']) + exe "normal 2l\<C-V>2jrx" + call assert_equal(['a x', ' x', 'b x'], getline(1, '$')) + " visual characterwise selection replace after eol + %d _ + call setline(1, 'a') + normal 4lv2lrx + call assert_equal('a xxx', getline(1)) bwipe! set virtualedit= endfunc @@ -346,6 +360,48 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" Test for delete that breaks a tab into spaces +func Test_delete_break_tab() + new + call setline(1, "one\ttwo") + set virtualedit=all + normal v3ld + call assert_equal(' two', getline(1)) + set virtualedit& + close! +endfunc + +" Test for using <BS>, <C-W> and <C-U> in virtual edit mode +" to erase character, word and line. +func Test_ve_backspace() + new + call setline(1, 'sample') + set virtualedit=all + set backspace=indent,eol,start + exe "normal 15|i\<BS>\<BS>" + call assert_equal([0, 1, 7, 5], getpos('.')) + exe "normal 15|i\<C-W>" + call assert_equal([0, 1, 6, 0], getpos('.')) + exe "normal 15|i\<C-U>" + call assert_equal([0, 1, 1, 0], getpos('.')) + set backspace& + set virtualedit& + close! +endfunc + +" Test for delete (x) on EOL character and after EOL +func Test_delete_past_eol() + new + call setline(1, "ab") + set virtualedit=all + exe "normal 2lx" + call assert_equal('ab', getline(1)) + exe "normal 10lx" + call assert_equal('ab', getline(1)) + set virtualedit& + bw! +endfunc + " After calling s:TryVirtualeditReplace(), line 1 will contain one of these " two strings, depending on whether virtual editing is on or off. let s:result_ve_on = 'a x' diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 7fb34ec81f..712ea343ed 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -225,6 +225,15 @@ func Test_virtual_replace() exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR" call assert_equal(['AB......CDEFGHI.Jkl', \ 'AB IJKLMNO QRst'], getline(12, 13)) + + " Test inserting Tab with 'noexpandtab' and 'softabstop' set to 4 + %d + call setline(1, 'aaaaaaaaaaaaa') + set softtabstop=4 + exe "normal gggR\<Tab>\<Tab>x" + call assert_equal("\txaaaa", getline(1)) + set softtabstop& + enew! set noai bs&vim if exists('save_t_kD') @@ -1281,7 +1290,7 @@ func Test_visual_block_with_virtualedit() endfunc func Test_visual_block_ctrl_w_f() - " Emtpy block selected in new buffer should not result in an error. + " Empty block selected in new buffer should not result in an error. au! BufNew foo sil norm f edit foo diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index f4878c2397..643c1068bd 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -115,7 +115,7 @@ func Test_winbuf_close() call assert_equal('Xtest2', bufname('%')) quit! call assert_equal('Xtest3', bufname('%')) - call assert_fails('silent! quit!', 'E162') + call assert_fails('silent! quit!', 'E37') call assert_equal('Xtest1', bufname('%')) call delete('Xtest1') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index c4ce4d638c..20564fed66 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -501,10 +501,13 @@ func Test_win_screenpos() call assert_equal([1, 32], win_screenpos(2)) call assert_equal([12, 1], win_screenpos(3)) call assert_equal([0, 0], win_screenpos(4)) + call assert_fails('let l = win_screenpos([])', 'E745:') only endfunc func Test_window_jump_tag() + CheckFeature quickfix + help /iccf call assert_match('^|iccf|', getline('.')) @@ -542,6 +545,7 @@ func Test_window_newtab() call assert_equal(2, tabpagenr('$')) call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)')) call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)')) + call assert_equal(['Xc' ], map(tabpagebuflist(), 'bufname(v:val)')) %bw! endfunc @@ -592,6 +596,11 @@ func Test_window_contents() call assert_equal(59, line("w0")) call assert_equal('59 ', s3) + %d + call setline(1, ['one', 'two', 'three']) + call assert_equal(1, line('w0')) + call assert_equal(3, line('w$')) + bwipeout! call test_garbagecollect_now() endfunc @@ -728,6 +737,7 @@ func Test_relative_cursor_position_in_one_line_window() only! bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_relative_cursor_position_after_move_and_resize() @@ -904,6 +914,10 @@ func Test_winnr() call assert_fails("echo winnr('ll')", 'E15:') call assert_fails("echo winnr('5')", 'E15:') call assert_equal(4, winnr('0h')) + call assert_fails("let w = winnr([])", 'E730:') + call assert_equal('unknown', win_gettype(-1)) + call assert_equal(-1, winheight(-1)) + call assert_equal(-1, winwidth(-1)) tabnew call assert_equal(8, tabpagewinnr(1, 'j')) @@ -924,9 +938,12 @@ func Test_winrestview() call assert_equal(view, winsaveview()) bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_win_splitmove() + CheckFeature quickfix + edit a leftabove split b leftabove vsplit c @@ -952,6 +969,7 @@ func Test_win_splitmove() call assert_equal(bufname(winbufnr(2)), 'b') call assert_equal(bufname(winbufnr(3)), 'a') call assert_equal(bufname(winbufnr(4)), 'd') + call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:') only | bd call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') @@ -1124,6 +1142,18 @@ func Test_split_cmds_with_no_room() call Run_noroom_for_newwindow_test('v') endfunc +" Test for various wincmd failures +func Test_wincmd_fails() + only! + call assert_beeps("normal \<C-W>w") + call assert_beeps("normal \<C-W>p") + call assert_beeps("normal \<C-W>gk") + call assert_beeps("normal \<C-W>r") + call assert_beeps("normal \<C-W>K") + call assert_beeps("normal \<C-W>H") + call assert_beeps("normal \<C-W>2gt") +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit @@ -1209,7 +1239,7 @@ endfunc " Test for jumping to a vertical/horizontal neighbor window based on the " current cursor position -func Test_window_goto_neightbor() +func Test_window_goto_neighbor() %bw! " Vertical window movement @@ -1388,17 +1418,20 @@ func Test_win_move_separator() call assert_equal(w0, winwidth(0)) call assert_true(win_move_separator(0, -1)) call assert_equal(w0, winwidth(0)) + " check that win_move_separator doesn't error with offsets beyond moving " possibility call assert_true(win_move_separator(id, 5000)) call assert_true(winwidth(id) > w) call assert_true(win_move_separator(id, -5000)) call assert_true(winwidth(id) < w) + " check that win_move_separator returns false for an invalid window wincmd = let w = winwidth(0) call assert_false(win_move_separator(-1, 1)) call assert_equal(w, winwidth(0)) + " check that win_move_separator returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1406,6 +1439,13 @@ func Test_win_move_separator() call assert_false(win_move_separator(id, 1)) call assert_equal(w, winwidth(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_separator(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc @@ -1454,17 +1494,20 @@ func Test_win_move_statusline() call assert_true(id->win_move_statusline(-offset)) call assert_equal(h, winheight(id)) endfor + " check that win_move_statusline doesn't error with offsets beyond moving " possibility call assert_true(win_move_statusline(id, 5000)) call assert_true(winheight(id) > h) call assert_true(win_move_statusline(id, -5000)) call assert_true(winheight(id) < h) + " check that win_move_statusline returns false for an invalid window wincmd = let h = winheight(0) call assert_false(win_move_statusline(-1, 1)) call assert_equal(h, winheight(0)) + " check that win_move_statusline returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1472,6 +1515,13 @@ func Test_win_move_statusline() call assert_false(win_move_statusline(id, 1)) call assert_equal(h, winheight(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_statusline(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc diff --git a/src/nvim/testdir/test_window_id.vim b/src/nvim/testdir/test_window_id.vim index 8bf4ede350..396a49b55f 100644 --- a/src/nvim/testdir/test_window_id.vim +++ b/src/nvim/testdir/test_window_id.vim @@ -1,5 +1,7 @@ " Test using the window ID. +source check.vim + func Test_win_getid() edit one let id1 = win_getid() @@ -90,10 +92,16 @@ func Test_win_getid() split call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5))) + call assert_fails('let w = win_getid([])', 'E745:') + call assert_equal(0, win_getid(-1)) + call assert_equal(-1, win_getid(1, -1)) + only! endfunc func Test_win_getid_curtab() + CheckFeature quickfix + tabedit X tabfirst copen @@ -127,4 +135,8 @@ func Test_winlayout() let w2 = win_getid() call assert_equal(['leaf', w2], 2->winlayout()) tabclose + + call assert_equal([], winlayout(-1)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index adc05ab979..8fb4c8fa4c 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -260,6 +260,193 @@ func Test_write_errors() close call delete('Xfile') + + " Nvim treats NULL list/blob more like empty list/blob + " call writefile(v:_null_list, 'Xfile') + " call assert_false(filereadable('Xfile')) + " call writefile(v:_null_blob, 'Xfile') + " call assert_false(filereadable('Xfile')) + call assert_fails('call writefile([], "")', 'E482:') + + " very long file name + let long_fname = repeat('n', 5000) + call assert_fails('exe "w " .. long_fname', 'E75:') + call assert_fails('call writefile([], long_fname)', 'E482:') +endfunc + +" Test for writing to a file which is modified after Vim read it +func Test_write_file_mtime() + CheckEnglish + CheckRunVimInTerminal + + " First read the file into a buffer + call writefile(["Line1", "Line2"], 'Xfile') + let old_ftime = getftime('Xfile') + let buf = RunVimInTerminal('Xfile', #{rows : 10}) + call term_wait(buf) + call term_sendkeys(buf, ":set noswapfile\<CR>") + call term_wait(buf) + + " Modify the file directly. Make sure the file modification time is + " different. Note that on Linux/Unix, the file is considered modified + " outside, only if the difference is 2 seconds or more + sleep 1 + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + while new_ftime - old_ftime < 2 + sleep 100m + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + endwhile + + " Try to overwrite the file and check for the prompt + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal("WARNING: The file has been changed since reading it!!!", term_getline(buf, 9))}) + call assert_equal("Do you really want to write to it (y/n)?", + \ term_getline(buf, 10)) + call term_sendkeys(buf, "n\<CR>") + call term_wait(buf) + call assert_equal(new_ftime, getftime('Xfile')) + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call term_sendkeys(buf, "y\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal('Line2', readfile('Xfile')[1])}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xfile') +endfunc + +" Test for an autocmd unloading a buffer during a write command +func Test_write_autocmd_unloadbuf_lockmark() + augroup WriteTest + autocmd BufWritePre Xfile enew | write + augroup END + e Xfile + call assert_fails('lockmarks write', ['E32', 'E203:']) + augroup WriteTest + au! + augroup END + augroup! WriteTest +endfunc + +" Test for writing a buffer with 'acwrite' but without autocmds +func Test_write_acwrite_error() + new Xfile + call setline(1, ['line1', 'line2', 'line3']) + set buftype=acwrite + call assert_fails('write', 'E676:') + call assert_fails('1,2write!', 'E676:') + call assert_fails('w >>', 'E676:') + close! +endfunc + +" Test for adding and removing lines from an autocmd when writing a buffer +func Test_write_autocmd_add_remove_lines() + new Xfile + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + + " Autocmd deleting lines from the file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile 1,2d + augroup END + call assert_fails('2,3w!', 'E204:') + + " Autocmd adding lines to a file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile call append(0, ['xxx', 'yyy']) + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + 1,2w! + call assert_equal(['xxx', 'yyy', 'aaa', 'bbb'], readfile('Xfile')) + + " Autocmd deleting lines from the file when writing the whole file + augroup WriteTest2 + au! + autocmd BufWritePre Xfile 1,2d + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + w + call assert_equal(['ccc', 'ddd'], readfile('Xfile')) + + augroup WriteTest2 + au! + augroup END + augroup! WriteTest2 + + close! + call delete('Xfile') +endfunc + +" Test for writing to a readonly file +func Test_write_readonly() + " In Cirrus-CI, the freebsd tests are run under a root account. So this test + " doesn't fail. + CheckNotBSD + call writefile([], 'Xfile') + call setfperm('Xfile', "r--------") + edit Xfile + set noreadonly + call assert_fails('write', 'E505:') + let save_cpo = &cpo + set cpo+=W + call assert_fails('write!', 'E504:') + let &cpo = save_cpo + call setline(1, ['line1']) + write! + call assert_equal(['line1'], readfile('Xfile')) + call delete('Xfile') +endfunc + +" Test for 'patchmode' +func Test_patchmode() + CheckNotBSD + call writefile(['one'], 'Xfile') + set patchmode=.orig nobackup writebackup + new Xfile + call setline(1, 'two') + " first write should create the .orig file + write + " TODO: Xfile.orig is not created in Cirrus FreeBSD CI test + call assert_equal(['one'], readfile('Xfile.orig')) + call setline(1, 'three') + " subsequent writes should not create/modify the .orig file + write + call assert_equal(['one'], readfile('Xfile.orig')) + set patchmode& backup& writebackup& + call delete('Xfile') + call delete('Xfile.orig') +endfunc + +" Test for writing to a file in a readonly directory +func Test_write_readonly_dir() + if !has('unix') || has('bsd') + " On MS-Windows, modifying files in a read-only directory is allowed. + " In Cirrus-CI for Freebsd, tests are run under a root account where + " modifying files in a read-only directory are allowed. + return + endif + call mkdir('Xdir') + call writefile(['one'], 'Xdir/Xfile1') + call setfperm('Xdir', 'r-xr--r--') + " try to create a new file in the directory + new Xdir/Xfile2 + call setline(1, 'two') + call assert_fails('write', 'E212:') + " try to create a backup file in the directory + edit! Xdir/Xfile1 + set backupdir=./Xdir + set patchmode=.orig + call assert_fails('write', 'E509:') + call setfperm('Xdir', 'rwxr--r--') + call delete('Xdir', 'rf') + set backupdir& patchmode& endfunc " Test for writing a file using invalid file encoding diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 348d5c6e29..9dd41224da 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -14,6 +14,15 @@ # include "testing.c.generated.h" #endif +static char e_assert_fails_second_arg[] + = N_("E856: assert_fails() second argument must be a string or a list with one or two strings"); +static char e_assert_fails_fourth_argument[] + = N_("E1115: assert_fails() fourth argument must be a number"); +static char e_assert_fails_fifth_argument[] + = N_("E1116: assert_fails() fifth argument must be a string"); +static char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[] + = N_("E1142: Calling test_garbagecollect_now() while v:testing is not set"); + /// Prepare "gap" for an assert error and add the sourcing position. static void prepare_assert_error(garray_T *gap) { @@ -65,7 +74,7 @@ static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) case '\\': ga_concat(gap, "\\\\"); break; default: - if (*p < ' ') { + if (*p < ' ' || *p == 0x7f) { vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); ga_concat(gap, (char *)buf); } else { @@ -121,7 +130,10 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s bool did_copy = false; int omitted = 0; - if (opt_msg_tv->v_type != VAR_UNKNOWN) { + if (opt_msg_tv->v_type != VAR_UNKNOWN + && !(opt_msg_tv->v_type == VAR_STRING + && (opt_msg_tv->vval.v_string == NULL + || *opt_msg_tv->vval.v_string == NUL))) { tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); ga_concat(gap, (char *)tofree); xfree(tofree); @@ -241,13 +253,12 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char *)pat, (char *)text, false) - != (atype == ASSERT_MATCH)) { + if (called_emsg == called_emsg_before + && pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) { garray_T ga; prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); @@ -348,11 +359,12 @@ static int assert_equalfile(typval_T *argvars) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); garray_T ga; - if (fname1 == NULL || fname2 == NULL) { + if (called_emsg > called_emsg_before) { return 0; } @@ -474,11 +486,13 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) garray_T ga; int save_trylevel = trylevel; const int called_emsg_before = called_emsg; + char *wrong_arg_msg = NULL; // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; suppress_errthrow = true; emsg_silent = true; + emsg_assert_fails_used = true; do_cmdline_cmd(cmd); if (called_emsg == called_emsg_before) { @@ -490,13 +504,75 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = 1; } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + const char *expected; + bool error_found = false; + int error_found_index = 1; + char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg; + + if (argvars[1].v_type == VAR_STRING) { + expected = tv_get_string_buf_chk(&argvars[1], buf); + error_found = expected == NULL || strstr(actual, expected) == NULL; + } else if (argvars[1].v_type == VAR_LIST) { + const list_T *const list = argvars[1].vval.v_list; + if (list == NULL || tv_list_len(list) < 1 || tv_list_len(list) > 2) { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list)); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } else if (tv_list_len(list) == 2) { + tv = TV_LIST_ITEM_TV(tv_list_last(list)); + actual = get_vim_var_str(VV_ERRMSG); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } + } + } else { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + + if (!error_found && argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + if (argvars[3].v_type != VAR_NUMBER) { + wrong_arg_msg = e_assert_fails_fourth_argument; + goto theend; + } else if (argvars[3].vval.v_number >= 0 + && argvars[3].vval.v_number != emsg_assert_fails_lnum) { + error_found = true; + error_found_index = 3; + } + if (!error_found && argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_STRING) { + wrong_arg_msg = e_assert_fails_fifth_argument; + goto theend; + } else if (argvars[4].vval.v_string != NULL + && !pattern_match(argvars[4].vval.v_string, + emsg_assert_fails_context, false)) { + error_found = true; + error_found_index = 4; + } + } + } - if (error == NULL - || strstr(get_vim_var_str(VV_ERRMSG), error) == NULL) { + if (error_found) { + typval_T actual_tv; prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + if (error_found_index == 3) { + actual_tv.v_type = VAR_NUMBER; + actual_tv.vval.v_number = emsg_assert_fails_lnum; + } else if (error_found_index == 4) { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = emsg_assert_fails_context; + } else { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = actual; + } + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[error_found_index], &actual_tv, ASSERT_OTHER); ga_concat(&ga, ": "); assert_append_cmd_or_arg(&ga, argvars, cmd); assert_error(&ga); @@ -505,11 +581,17 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +theend: trylevel = save_trylevel; suppress_errthrow = false; emsg_silent = false; emsg_on_display = false; + emsg_assert_fails_used = false; + XFREE_CLEAR(emsg_assert_fails_msg); set_vim_var_string(VV_ERRMSG, NULL, 0); + if (wrong_arg_msg != NULL) { + emsg(_(wrong_arg_msg)); + } } // "assert_false(actual[, msg])" function @@ -614,7 +696,11 @@ void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData { // This is dangerous, any Lists and Dicts used internally may be freed // while still in use. - garbage_collect(true); + if (!get_vim_var_nr(VV_TESTING)) { + emsg(_(e_calling_test_garbagecollect_now_while_v_testing_is_not_set)); + } else { + garbage_collect(true); + } } /// "test_write_list_log()" function diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index dbe7110d60..8290fe14e5 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -229,7 +229,7 @@ bool findpar(bool *pincl, int dir, long count, int what, bool both) } /// check if the string 's' is a nroff macro that is in option 'opt' -static bool inmacro(char_u *opt, char_u *s) +static bool inmacro(char_u *opt, const char_u *s) { char_u *macro; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 1cb1c34ad3..083677a58e 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -2099,7 +2099,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, // Dickey ncurses terminfo does not include the setrgbf and setrgbb // capabilities, proposed by Rüdiger Sonderfeld on 2013-10-15. Adding // them here when terminfo lacks them is an augmentation, not a fixup. - // https://gist.github.com/XVilka/8346728 + // https://github.com/termstandard/colors // At this time (2017-07-12) it seems like all terminals that support rgb // color codes can use semicolons in the terminal code and be fine. diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 2c9510bf34..eb59d72e43 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -322,7 +322,7 @@ void vim_beep(unsigned val) // Only beep up to three times per half a second, // otherwise a sequence of beeps would freeze Vim. - if (start_time == 0 || os_hrtime() - start_time > 500000000u) { + if (start_time == 0 || os_hrtime() - start_time > 500000000U) { beeps = 0; start_time = os_hrtime(); } @@ -452,7 +452,7 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, MIN(clearcol, (int)grid->cols - 1)); ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); - os_microdelay(wd * 1000u, true); + os_microdelay(wd * 1000U, true); pending_cursor_update = true; // restore the cursor later } } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 47c83b8ed1..84e1a5e513 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -505,7 +505,7 @@ static void debug_delay(Integer lines) ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1); - os_microdelay(factor * wd * 1000u, true); + os_microdelay(factor * wd * 1000U, true); } static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol) diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 4c2ea75047..bed4d55d4e 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -36,8 +36,7 @@ static char e_no_such_user_defined_command_in_current_buffer_str[] /// List of names for completion for ":command" with the EXPAND_ flag. /// Must be alphabetical for completion. -static const char *command_complete[] = -{ +static const char *command_complete[] = { [EXPAND_ARGLIST] = "arglist", [EXPAND_AUGROUP] = "augroup", [EXPAND_BEHAVE] = "behave", @@ -86,8 +85,7 @@ static struct { cmd_addr_T expand; char *name; char *shortname; -} addr_type_complete[] = -{ +} addr_type_complete[] = { { ADDR_ARGUMENTS, "arguments", "arg" }, { ADDR_LINES, "lines", "line" }, { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, @@ -1077,7 +1075,7 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, } /// split and quote args for <f-args> -static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) +static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp) { char *buf; char *p; @@ -1217,7 +1215,7 @@ static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) /// was added. /// /// @return the number of bytes added -size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) +size_t add_win_cmd_modifiers(char *buf, const cmdmod_T *cmod, bool *multi_mods) { size_t result = 0; @@ -1322,7 +1320,7 @@ size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) } } // flags from cmod->cmod_split - result += add_win_cmd_modifers(buf, cmod, &multi_mods); + result += add_win_cmd_modifiers(buf, cmod, &multi_mods); if (quote && buf != NULL) { buf += result - 2; diff --git a/src/nvim/version.c b/src/nvim/version.c index f0e2f92ba4..98f34ca11f 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -34,7 +34,7 @@ "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH) \ NVIM_VERSION_PRERELEASE #endif -#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM +#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM // NOLINT(bugprone-suspicious-missing-comma) char *Version = VIM_VERSION_SHORT; char *longVersion = NVIM_VERSION_LONG; @@ -71,13 +71,587 @@ static char *features[] = { // clang-format off static const int included_patches[] = { - 1850, + 2424, + 2423, + 2422, + 2421, + // 2420, + 2419, + // 2418, + 2417, + 2416, + // 2415, + 2414, + 2413, + 2412, + 2411, + 2410, + 2409, + 2408, + 2407, + 2406, + 2405, + 2404, + // 2403, + 2402, + 2401, + 2400, + // 2399, + 2398, + 2397, + 2396, + 2395, + 2394, + 2393, + 2392, + 2391, + 2390, + 2389, + 2388, + 2387, + // 2386, + 2385, + 2384, + 2383, + 2382, + // 2381, + 2380, + 2379, + 2378, + 2377, + 2376, + 2375, + 2374, + // 2373, + 2372, + // 2371, + 2370, + // 2369, + 2368, + 2367, + 2366, + 2365, + 2364, + 2363, + // 2362, + 2361, + 2360, + 2359, + 2358, + 2357, + 2356, + 2355, + 2354, + 2353, + 2352, + // 2351, + 2350, + 2349, + 2348, + 2347, + 2346, + 2345, + 2344, + 2343, + 2342, + 2341, + 2340, + 2339, + 2338, + // 2337, + 2336, + 2335, + // 2334, + 2333, + 2332, + 2331, + 2330, + 2329, + 2328, + 2327, + 2326, + 2325, + // 2324, + 2323, + 2322, + 2321, + 2320, + 2319, + 2318, + 2317, + 2316, + 2315, + 2314, + 2313, + 2312, + 2311, + 2310, + 2309, + // 2308, + // 2307, + 2306, + 2305, + 2304, + 2303, + 2302, + 2301, + // 2300, + // 2299, + // 2298, + 2297, + // 2296, + // 2295, + 2294, + 2293, + // 2292, + 2291, + 2290, + 2289, + // 2288, + // 2287, + // 2286, + 2285, + 2284, + 2283, + 2282, + 2281, + 2280, + // 2279, + 2278, + // 2277, + // 2276, + 2275, + 2274, + // 2273, + 2272, + 2271, + 2270, + 2269, + 2268, + 2267, + // 2266, + // 2265, + 2264, + 2263, + 2262, + 2261, + 2260, + 2259, + 2258, + 2257, + 2256, + 2255, + 2254, + 2253, + 2252, + // 2251, + // 2250, + 2249, + 2248, + 2247, + 2246, + 2245, + 2244, + 2243, + 2242, + // 2241, + // 2240, + 2239, + 2238, + 2237, + 2236, + 2235, + 2234, + 2233, + // 2232, + 2231, + // 2230, + 2229, + 2228, + 2227, + 2226, + 2225, + 2224, + 2223, + 2222, + 2221, + 2220, + // 2219, + 2218, + 2217, + // 2216, + // 2215, + 2214, + // 2213, + 2212, + // 2211, + // 2210, + 2209, + // 2208, + 2207, + 2206, + 2205, + 2204, + 2203, + 2202, + 2201, + // 2200, + 2199, + 2198, + 2197, + 2196, + // 2195, + // 2194, + // 2193, + // 2192, + 2191, + 2190, + // 2189, + 2188, + 2187, + 2186, + 2185, + 2184, + 2183, + 2182, + // 2181, + 2180, + 2179, + 2178, + 2177, + // 2176, + 2175, + 2174, + 2173, + 2172, + 2171, + 2170, + 2169, + 2168, + 2167, + 2166, + 2165, + // 2164, + 2163, + 2162, + 2161, + 2160, + 2159, + // 2158, + 2157, + // 2156, + // 2155, + 2154, + // 2153, + 2152, + 2151, + 2150, + 2149, + // 2148, + 2147, + // 2146, + 2145, + 2144, + 2143, + // 2142, + 2141, + 2140, + // 2139, + 2138, + 2137, + 2136, + 2135, + 2134, + 2133, + 2132, + 2131, + 2130, + 2129, + 2128, + // 2127, + 2126, + 2125, + 2124, + 2123, + 2122, + // 2121, + 2120, + 2119, + 2118, + 2117, + 2116, + 2115, + // 2114, + 2113, + 2112, + 2111, + // 2110, + // 2109, + 2108, + // 2107, + 2106, + 2105, + 2104, + 2103, + 2102, + 2101, + 2100, + // 2099, + 2098, + 2097, + 2096, + 2095, + // 2094, + // 2093, + // 2092, + 2091, + 2090, + 2089, + 2088, + 2087, + 2086, + // 2085, + 2084, + 2083, + 2082, + 2081, + // 2080, + 2079, + 2078, + // 2077, + // 2076, + 2075, + 2074, + 2073, + 2072, + // 2071, + // 2070, + // 2069, + // 2068, + // 2067, + // 2066, + 2065, + 2064, + 2063, + // 2062, + 2061, + 2060, + 2059, + 2058, + 2057, + 2056, + 2055, + 2054, + // 2053, + 2052, + 2051, + 2050, + 2049, + // 2048, + // 2047, + // 2046, + 2045, + // 2044, + 2043, + 2042, + 2041, + // 2040, + // 2039, + 2038, + 2037, + 2036, + 2035, + 2034, + 2033, + // 2032, + 2031, + 2030, + 2029, + 2028, + 2027, + 2026, + 2025, + 2024, + 2023, + // 2022, + // 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013, + 2012, + 2011, + 2010, + // 2009, + // 2008, + 2007, + 2006, + 2005, + // 2004, + 2003, + 2002, + // 2001, + 2000, + // 1999, + // 1998, + // 1997, + // 1996, + 1995, + 1994, + // 1993, + 1992, + 1991, + 1990, + // 1989, + 1988, + // 1987, + // 1986, + // 1985, + 1984, + 1983, + // 1982, + // 1981, + 1980, + // 1979, + // 1978, + 1977, + 1976, + 1975, + 1974, + 1973, + 1972, + 1971, + 1970, + // 1969, + // 1968, + 1967, + 1966, + 1965, + // 1964, + // 1963, + 1962, + 1961, + 1960, + // 1959, + 1958, + // 1957, + 1956, + 1955, + // 1954, + 1953, + 1952, + 1951, + 1950, + // 1949, + 1948, + 1947, + 1946, + // 1945, + // 1944, + // 1943, + 1942, + 1941, + // 1940, + // 1939, + 1938, + 1937, + // 1936, + 1935, + // 1934, + 1933, + 1932, + 1931, + 1930, + // 1929, + // 1928, + 1927, + 1926, + 1925, + 1924, + 1923, + 1922, + 1921, + // 1920, + // 1919, + // 1918, + // 1917, + 1916, + 1915, + 1914, + 1913, + 1912, + 1911, + 1910, + 1909, + // 1908, + // 1907, + // 1906, + // 1905, + // 1904, + 1903, + // 1902, + 1901, + 1900, + 1899, + 1898, + 1897, + 1896, + 1895, + 1894, + 1893, + // 1892, + // 1891, + 1890, + 1889, + 1888, + 1887, + 1886, + 1885, + // 1884, + 1883, + // 1882, + 1881, + // 1880, + 1879, + 1878, + 1877, + 1876, + 1875, + // 1874, + 1873, + 1872, + // 1871, + 1870, + 1869, + 1868, + 1867, + // 1866, + 1865, + 1864, + 1863, + 1862, + 1861, + 1860, + 1859, + 1858, + 1857, + 1856, + 1855, + 1854, + // 1853, + 1852, + // 1851, + // 1850, 1849, 1848, 1847, 1846, - 1845, - 1844, + // 1845, + // 1844, 1843, 1842, 1841, @@ -94,7 +668,7 @@ static const int included_patches[] = { 1830, 1829, 1828, - 1827, + // 1827, 1826, 1825, 1824, @@ -102,58 +676,58 @@ static const int included_patches[] = { 1822, 1821, 1820, - 1819, + // 1819, 1818, 1817, 1816, 1815, - 1814, - 1813, + // 1814, + // 1813, 1812, - 1811, - 1810, + // 1811, + // 1810, 1809, 1808, 1807, 1806, 1805, - // 1804, + 1804, 1803, - 1802, + // 1802, 1801, 1800, - 1799, + // 1799, 1798, 1797, 1796, 1795, - // 1794, + 1794, 1793, 1792, 1791, 1790, - 1789, + // 1789, 1788, - 1787, - 1786, + // 1787, + // 1786, 1785, - 1784, + // 1784, 1783, 1782, 1781, 1780, - 1779, - 1778, + // 1779, + // 1778, 1777, 1776, 1775, 1774, - 1773, + // 1773, 1772, 1771, - 1770, + // 1770, 1769, - 1768, + // 1768, 1767, 1766, 1765, @@ -166,34 +740,34 @@ static const int included_patches[] = { 1758, 1757, 1756, - 1755, - 1754, - 1753, - 1752, - 1751, + // 1755, + // 1754, + // 1753, + // 1752, + // 1751, 1750, 1749, 1748, 1747, 1746, 1745, - // 1744, - // 1743, + 1744, + 1743, 1742, 1741, 1740, 1739, 1738, 1737, - 1736, + // 1736, 1735, 1734, - 1733, - // 1732, + // 1733, + 1732, 1731, - 1730, + // 1730, 1729, - 1728, + // 1728, 1727, 1726, 1725, @@ -202,27 +776,27 @@ static const int included_patches[] = { 1722, 1721, 1720, - 1719, - 1718, + // 1719, + // 1718, 1717, 1716, 1715, - 1714, - 1713, + // 1714, + // 1713, 1712, - 1711, - 1710, - 1709, + // 1711, + // 1710, + // 1709, 1708, - 1707, + // 1707, 1706, 1705, 1704, - 1703, + // 1703, 1702, 1701, - 1700, - 1699, + // 1700, + // 1699, 1698, 1697, 1696, @@ -231,7 +805,7 @@ static const int included_patches[] = { 1693, 1692, 1691, - 1690, + // 1690, 1689, 1688, 1687, @@ -240,140 +814,140 @@ static const int included_patches[] = { 1684, 1683, 1682, - 1681, + // 1681, 1680, 1679, - 1678, + // 1678, 1677, - 1676, - 1675, + // 1676, + // 1675, 1674, - 1673, + // 1673, 1672, 1671, 1670, 1669, 1668, 1667, - 1666, - 1665, + // 1666, + // 1665, 1664, - 1663, + // 1663, 1662, 1661, 1660, - 1659, + // 1659, 1658, 1657, - 1656, - 1655, + // 1656, + // 1655, 1654, 1653, 1652, 1651, 1650, - 1649, + // 1649, 1648, 1647, - 1646, + // 1646, 1645, 1644, 1643, 1642, - 1641, + // 1641, 1640, 1639, 1638, 1637, - 1636, + // 1636, 1635, 1634, 1633, 1632, 1631, 1630, - 1629, - 1628, + // 1629, + // 1628, 1627, - 1626, + // 1626, 1625, 1624, 1623, - 1622, + // 1622, 1621, - 1620, + // 1620, 1619, 1618, - 1617, + // 1617, 1616, - 1615, + // 1615, 1614, 1613, - 1612, + // 1612, 1611, 1610, - 1609, + // 1609, 1608, - 1607, + // 1607, 1606, 1605, 1604, - 1603, - 1602, + // 1603, + // 1602, 1601, - 1600, - 1599, + // 1600, + // 1599, 1598, - 1597, - 1596, + // 1597, + // 1596, 1595, 1594, 1593, - // 1592, + 1592, 1591, 1590, - 1589, + // 1589, 1588, 1587, - 1586, + // 1586, 1585, - 1584, - 1583, + // 1584, + // 1583, 1582, 1581, - 1580, + // 1580, 1579, 1578, - 1577, + // 1577, 1576, 1575, - 1574, - 1573, + // 1574, + // 1573, 1572, - 1571, + // 1571, 1570, 1569, 1568, 1567, 1566, - 1565, + // 1565, 1564, 1563, - 1562, - 1561, - 1560, - 1559, - 1558, + // 1562, + // 1561, + // 1560, + // 1559, + // 1558, 1557, 1556, - 1555, + // 1555, 1554, - 1553, + // 1553, 1552, - 1551, - 1550, + // 1551, + // 1550, 1549, - 1548, + // 1548, 1547, 1546, 1545, @@ -383,59 +957,59 @@ static const int included_patches[] = { 1541, 1540, 1539, - 1538, - 1537, + // 1538, + // 1537, 1536, 1535, - 1534, + // 1534, 1533, 1532, 1531, 1530, 1529, 1528, - 1527, - 1526, - 1525, + // 1527, + // 1526, + // 1525, 1524, - 1523, - 1522, - 1521, - 1520, + // 1523, + // 1522, + // 1521, + // 1520, 1519, - 1518, - 1517, + // 1518, + // 1517, 1516, - 1515, + // 1515, 1514, - 1513, + // 1513, 1512, - 1511, + // 1511, 1510, 1509, - 1508, + // 1508, 1507, 1506, 1505, 1504, 1503, - 1502, + // 1502, 1501, 1500, - 1499, + // 1499, 1498, - 1497, - 1496, - 1495, - 1494, - 1493, + // 1497, + // 1496, + // 1495, + // 1494, + // 1493, 1492, 1491, 1490, 1489, 1488, 1487, - 1486, + // 1486, 1485, 1484, 1483, @@ -448,7 +1022,7 @@ static const int included_patches[] = { 1476, 1475, 1474, - 1473, + // 1473, 1472, 1471, 1470, @@ -458,83 +1032,83 @@ static const int included_patches[] = { 1466, 1465, 1464, - 1463, + // 1463, 1462, 1461, - 1460, - 1459, + // 1460, + // 1459, 1458, 1457, 1456, - 1455, + // 1455, 1454, - 1453, - 1452, - 1451, - 1450, - 1449, - 1448, - 1447, - 1446, - 1445, - 1444, - 1443, - 1442, - 1441, + // 1453, + // 1452, + // 1451, + // 1450, + // 1449, + // 1448, + // 1447, + // 1446, + // 1445, + // 1444, + // 1443, + // 1442, + // 1441, 1440, 1439, - 1438, + // 1438, 1437, 1436, 1435, 1434, 1433, - 1432, - 1431, - 1430, - 1429, - 1428, - 1427, - 1426, + // 1432, + // 1431, + // 1430, + // 1429, + // 1428, + // 1427, + // 1426, 1425, 1424, - 1423, - 1422, - 1421, - 1420, - 1419, + // 1423, + // 1422, + // 1421, + // 1420, + // 1419, 1418, - 1417, - 1416, + // 1417, + // 1416, 1415, - 1414, - 1413, + // 1414, + // 1413, 1412, 1411, - 1410, + // 1410, 1409, - 1408, - 1407, - 1406, - 1405, + // 1408, + // 1407, + // 1406, + // 1405, 1404, 1403, - 1402, + // 1402, 1401, - 1400, - 1399, + // 1400, + // 1399, 1398, 1397, - 1396, - 1395, + // 1396, + // 1395, 1394, 1393, 1392, - 1391, + // 1391, 1390, - 1389, - 1388, - 1387, + // 1389, + // 1388, + // 1387, 1386, 1385, 1384, @@ -545,7 +1119,7 @@ static const int included_patches[] = { 1379, 1378, 1377, - 1376, + // 1376, 1375, 1374, 1373, @@ -557,12 +1131,12 @@ static const int included_patches[] = { 1367, 1366, 1365, - 1364, + // 1364, 1363, 1362, 1361, 1360, - 1359, + // 1359, 1358, 1357, 1356, @@ -570,38 +1144,38 @@ static const int included_patches[] = { 1354, 1353, 1352, - 1351, + // 1351, 1350, 1349, 1348, 1347, 1346, 1345, - 1344, - 1343, + // 1344, + // 1343, 1342, - 1341, - 1340, + // 1341, + // 1340, 1339, 1338, - 1337, + // 1337, 1336, - 1335, + // 1335, 1334, - 1333, - 1332, + // 1333, + // 1332, 1331, 1330, - 1329, - 1328, + // 1329, + // 1328, 1327, - 1326, + // 1326, 1325, 1324, 1323, 1322, - 1321, - 1320, + // 1321, + // 1320, 1319, 1318, 1317, @@ -621,7 +1195,7 @@ static const int included_patches[] = { 1303, 1302, 1301, - // 1300, + 1300, 1299, 1298, 1297, @@ -641,11 +1215,11 @@ static const int included_patches[] = { 1283, 1282, 1281, - 1280, + // 1280, 1279, - 1278, + // 1278, 1277, - 1276, + // 1276, 1275, 1274, 1273, @@ -654,15 +1228,15 @@ static const int included_patches[] = { 1270, 1269, 1268, - 1267, + // 1267, 1266, - 1265, + // 1265, 1264, 1263, 1262, 1261, 1260, - 1259, + // 1259, 1258, 1257, 1256, @@ -695,15 +1269,15 @@ static const int included_patches[] = { 1229, 1228, 1227, - 1226, + // 1226, 1225, - 1224, + // 1224, 1223, 1222, 1221, 1220, 1219, - 1218, + // 1218, 1217, 1216, 1215, @@ -721,7 +1295,7 @@ static const int included_patches[] = { 1203, 1202, 1201, - 1200, + // 1200, 1199, 1198, 1197, @@ -785,10 +1359,10 @@ static const int included_patches[] = { 1139, 1138, 1137, - 1136, + // 1136, 1135, 1134, - 1133, + // 1133, 1132, 1131, 1130, @@ -815,10 +1389,10 @@ static const int included_patches[] = { 1109, 1108, 1107, - 1106, + // 1106, 1105, 1104, - 1103, + // 1103, 1102, 1101, 1100, @@ -831,14 +1405,14 @@ static const int included_patches[] = { 1093, 1092, 1091, - 1090, + // 1090, 1089, 1088, 1087, 1086, 1085, 1084, - 1083, + // 1083, 1082, 1081, 1080, @@ -877,8 +1451,8 @@ static const int included_patches[] = { 1047, 1046, 1045, - 1044, - 1043, + // 1044, + // 1043, 1042, 1041, 1040, @@ -886,14 +1460,14 @@ static const int included_patches[] = { 1038, 1037, 1036, - 1035, + // 1035, 1034, 1033, 1032, 1031, 1030, 1029, - 1028, + // 1028, 1027, 1026, 1025, @@ -902,7 +1476,7 @@ static const int included_patches[] = { 1022, 1021, 1020, - 1019, + // 1019, 1018, 1017, 1016, @@ -917,7 +1491,7 @@ static const int included_patches[] = { 1007, 1006, 1005, - 1004, + // 1004, 1003, 1002, 1001, @@ -944,15 +1518,15 @@ static const int included_patches[] = { 980, 979, 978, - 977, + // 977, 976, 975, 974, 973, 972, 971, - 970, - 969, + // 970, + // 969, 968, 967, 966, @@ -968,7 +1542,7 @@ static const int included_patches[] = { 956, 955, 954, - 953, + // 953, 952, 951, 950, @@ -981,7 +1555,7 @@ static const int included_patches[] = { 943, 942, 941, - 940, + // 940, 939, 938, 937, @@ -993,7 +1567,7 @@ static const int included_patches[] = { 931, 930, 929, - 928, + // 928, 927, 926, 925, @@ -1003,19 +1577,19 @@ static const int included_patches[] = { 921, 920, 919, - 918, + // 918, 917, 916, 915, - 914, + // 914, 913, 912, 911, 910, - 909, + // 909, 908, 907, - 906, + // 906, 905, 904, 903, @@ -1026,12 +1600,12 @@ static const int included_patches[] = { 898, 897, 896, - 895, - 894, + // 895, + // 894, 893, 892, 891, - 890, + // 890, 889, 888, 887, @@ -1041,30 +1615,30 @@ static const int included_patches[] = { 883, 882, 881, - 880, - 879, + // 880, + // 879, 878, 877, - 876, + // 876, 875, 874, 873, 872, 871, - 870, + // 870, 869, 868, 867, 866, 865, 864, - 863, + // 863, 862, 861, 860, 859, 858, - 857, + // 857, 856, 855, 854, @@ -1076,7 +1650,7 @@ static const int included_patches[] = { 848, 847, 846, - 845, + // 845, 844, 843, 842, @@ -1097,16 +1671,16 @@ static const int included_patches[] = { 827, 826, 825, - 824, + // 824, 823, 822, 821, - 820, + // 820, 819, 818, 817, 816, - 815, + // 815, 814, 813, 812, @@ -1151,16 +1725,16 @@ static const int included_patches[] = { 773, 772, 771, - 770, + // 770, 769, - 768, + // 768, 767, 766, 765, 764, 763, 762, - 761, + // 761, 760, 759, 758, @@ -1211,14 +1785,14 @@ static const int included_patches[] = { 713, 712, 711, - 710, + // 710, 709, 708, - 707, + // 707, 706, 705, 704, - 703, + // 703, 702, 701, 700, @@ -1227,26 +1801,26 @@ static const int included_patches[] = { 697, 696, 695, - 694, + // 694, 693, 692, - 691, - 690, - 689, - 688, + // 691, + // 690, + // 689, + // 688, 687, 686, 685, - 684, + // 684, 683, - 682, - 681, + // 682, + // 681, 680, 679, 678, 677, - 676, - 675, + // 676, + // 675, 674, 673, 672, @@ -1254,11 +1828,11 @@ static const int included_patches[] = { 670, 669, 668, - 667, + // 667, 666, - 665, + // 665, 664, - 663, + // 663, 662, 661, 660, @@ -1266,8 +1840,8 @@ static const int included_patches[] = { 658, 657, 656, - 655, - 654, + // 655, + // 654, 653, 652, 651, @@ -1278,16 +1852,16 @@ static const int included_patches[] = { 646, 645, 644, - 643, + // 643, 642, 641, 640, - 639, - 638, + // 639, + // 638, 637, - 636, + // 636, 635, - 634, + // 634, 633, 632, 631, @@ -1309,7 +1883,7 @@ static const int included_patches[] = { 615, 614, 613, - 612, + // 612, 611, 610, 609, @@ -1320,7 +1894,7 @@ static const int included_patches[] = { 604, 603, 602, - 601, + // 601, 600, 599, 598, @@ -1339,10 +1913,10 @@ static const int included_patches[] = { 585, 584, 583, - 582, + // 582, 581, 580, - 579, + // 579, 578, 577, 576, @@ -1496,7 +2070,7 @@ static const int included_patches[] = { 428, 427, 426, - 425, + // 425, 424, 423, 422, @@ -1554,7 +2128,7 @@ static const int included_patches[] = { 370, 369, 368, - 367, + // 367, 366, 365, 364, @@ -1593,7 +2167,7 @@ static const int included_patches[] = { 331, 330, 329, - 328, + // 328, 327, 326, 325, @@ -1609,7 +2183,7 @@ static const int included_patches[] = { 315, 314, 313, - 312, + // 312, 311, 310, 309, @@ -1617,7 +2191,7 @@ static const int included_patches[] = { 307, 306, 305, - 304, + // 304, 303, 302, 301, @@ -1763,7 +2337,7 @@ static const int included_patches[] = { 161, 160, 159, - 158, + // 158, 157, 156, 155, @@ -1834,7 +2408,7 @@ static const int included_patches[] = { 90, 89, 88, - 87, + // 87, 86, 85, 84, @@ -1849,21 +2423,21 @@ static const int included_patches[] = { 75, 74, 73, - 72, + // 72, 71, 70, 69, 68, 67, 66, - 65, + // 65, 64, 63, - 62, + // 62, 61, 60, 59, - 58, + // 58, 57, 56, 55, @@ -1871,8 +2445,8 @@ static const int included_patches[] = { 53, 52, 51, - 50, - 49, + // 50, + // 49, 48, 47, 46, @@ -1921,7 +2495,7 @@ static const int included_patches[] = { 3, 2, 1, - 0, + // 0, }; // clang-format on @@ -2239,7 +2813,7 @@ void intro_message(int colon) size_t lines_size = ARRAY_SIZE(lines); assert(lines_size <= LONG_MAX); - blanklines = Rows - ((long)lines_size - 1l); + blanklines = Rows - ((long)lines_size - 1L); // Don't overwrite a statusline. Depends on 'cmdheight'. if (p_ls > 1) { diff --git a/src/nvim/version.h b/src/nvim/version.h index a19e863152..484350edee 100644 --- a/src/nvim/version.h +++ b/src/nvim/version.h @@ -14,7 +14,7 @@ extern char *longVersion; // Values that change for a new release #define VIM_VERSION_MAJOR 8 -#define VIM_VERSION_MINOR 0 +#define VIM_VERSION_MINOR 1 // Values based on the above #define VIM_VERSION_MAJOR_STR STR(VIM_VERSION_MAJOR) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4564831824..8028950c07 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2120,6 +2120,22 @@ viml_pexpr_parse_process_token: assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } switch (tok_type) { case kExprLexMissing: case kExprLexSpacing: @@ -2141,22 +2157,6 @@ viml_pexpr_parse_process_token: HL_CUR_TOKEN(Register); break; } -#define SIMPLE_UB_OP(op) \ - case kExprLex##op: { \ - if (want_node == kENodeValue) { \ - /* Value level: assume unary operator. */ \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children); \ - HL_CUR_TOKEN(Unary##op); \ - } else { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ - ADD_OP_NODE(cur_node); \ - HL_CUR_TOKEN(Binary##op); \ - } \ - want_node = kENodeValue; \ - break; \ - } SIMPLE_UB_OP(Plus) SIMPLE_UB_OP(Minus) #undef SIMPLE_UB_OP diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 9d0bc9d468..77fbfa615f 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -357,7 +357,7 @@ typedef struct { int arg_len; } ExprASTError; -/// Structure representing complety AST for one expression +/// Structure representing complete AST for one expression typedef struct { /// When AST is not correct this message will be printed. /// diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 404dc5a0d1..56e8b2d32b 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -81,8 +81,8 @@ typedef struct { bool can_continuate; } ParserState; -static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line, - void *const cookie, ParserHighlight *const colors) +static inline void viml_parser_init(ParserState *ret_pstate, ParserLineGetter get_line, + void *cookie, ParserHighlight *colors) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); /// Initialize a new parser state instance @@ -109,7 +109,7 @@ static inline void viml_parser_init(ParserState *const ret_pstate, const ParserL kvi_init(ret_pstate->stack); } -static inline void viml_parser_destroy(ParserState *const pstate) +static inline void viml_parser_destroy(ParserState *pstate) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Free all memory allocated by the parser on heap @@ -127,8 +127,7 @@ static inline void viml_parser_destroy(ParserState *const pstate) kvi_destroy(pstate->stack); } -static inline void viml_preader_get_line(ParserInputReader *const preader, - ParserLine *const ret_pline) +static inline void viml_preader_get_line(ParserInputReader *preader, ParserLine *ret_pline) REAL_FATTR_NONNULL_ALL; /// Get one line from ParserInputReader @@ -152,8 +151,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, *ret_pline = pline; } -static inline bool viml_parser_get_remaining_line(ParserState *const pstate, - ParserLine *const ret_pline) +static inline bool viml_parser_get_remaining_line(ParserState *pstate, ParserLine *ret_pline) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get currently parsed line, shifted to pstate->pos.col @@ -178,8 +176,7 @@ static inline bool viml_parser_get_remaining_line(ParserState *const pstate, return ret_pline->data != NULL; } -static inline void viml_parser_advance(ParserState *const pstate, - const size_t len) +static inline void viml_parser_advance(ParserState *pstate, size_t len) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Advance position by a given number of bytes @@ -200,10 +197,8 @@ static inline void viml_parser_advance(ParserState *const pstate, const size_t l } } -static inline void viml_parser_highlight(ParserState *const pstate, - const ParserPosition start, - const size_t end_col, - const char *const group) +static inline void viml_parser_highlight(ParserState *pstate, ParserPosition start, size_t len, + const char *group) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Record highlighting of some region of text diff --git a/src/nvim/window.c b/src/nvim/window.c index 0e6bb7822a..c755f58c4f 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -785,7 +785,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) 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)); + sizeof fconfig.border_hl_ids) != 0); wp->w_float_config = fconfig; @@ -4213,6 +4213,8 @@ static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds) return FAIL; } } + + reset_dragwin(); tp->tp_curwin = curwin; tp->tp_prevwin = prevwin; tp->tp_firstwin = firstwin; @@ -4275,6 +4277,10 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a clear_cmdline = true; } + // If there was a click in a window, it won't be usable for a following + // drag. + reset_dragwin(); + // The tabpage line may have appeared or disappeared, may need to resize the frames for that. // When the Vim window was resized or ROWS_AVAIL changed need to update frame sizes too. if (curtab->tp_old_Rows_avail != ROWS_AVAIL || (old_off != firstwin->w_winrow)) { diff --git a/src/uncrustify.cfg b/src/uncrustify.cfg index 2b9396e635..f9e6617d40 100644 --- a/src/uncrustify.cfg +++ b/src/uncrustify.cfg @@ -125,7 +125,7 @@ sp_before_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. -sp_after_assign = ignore # ignore/add/remove/force/not_defined +sp_after_assign = force # ignore/add/remove/force/not_defined # Add or remove space in 'enum {'. # @@ -1642,7 +1642,7 @@ nl_end_of_file = force # ignore/add/remove/force/not_defined nl_end_of_file_min = 1 # unsigned number # Add or remove newline between '=' and '{'. -nl_assign_brace = ignore # ignore/add/remove/force/not_defined +nl_assign_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force/not_defined @@ -1714,7 +1714,7 @@ nl_try_brace = ignore # ignore/add/remove/force/not_defined nl_getset_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'for' and '{'. -nl_for_brace = ignore # ignore/add/remove/force/not_defined +nl_for_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) <here> {'. @@ -1738,7 +1738,7 @@ nl_brace_square = ignore # ignore/add/remove/force/not_defined nl_brace_fparen = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'while' and '{'. -nl_while_brace = ignore # ignore/add/remove/force/not_defined +nl_while_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force/not_defined @@ -1763,7 +1763,7 @@ nl_do_brace = remove # ignore/add/remove/force/not_defined nl_brace_while = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'switch' and '{'. -nl_switch_brace = ignore # ignore/add/remove/force/not_defined +nl_switch_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = ignore # ignore/add/remove/force/not_defined @@ -1777,7 +1777,7 @@ nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines -nl_multi_line_sparen_open = ignore # ignore/add/remove/force/not_defined +nl_multi_line_sparen_open = remove # ignore/add/remove/force/not_defined # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. @@ -1908,7 +1908,7 @@ nl_func_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # definition. -nl_func_def_paren = ignore # ignore/add/remove/force/not_defined +nl_func_def_paren = remove # ignore/add/remove/force/not_defined # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined @@ -1941,7 +1941,7 @@ nl_func_decl_start_multi_line = false # true/false nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. -nl_func_decl_args = ignore # ignore/add/remove/force/not_defined +nl_func_decl_args = remove # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in a function definition. nl_func_def_args = remove # ignore/add/remove/force/not_defined @@ -1958,7 +1958,7 @@ nl_func_decl_args_multi_line = false # true/false nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. -nl_func_decl_end = ignore # ignore/add/remove/force/not_defined +nl_func_decl_end = remove # ignore/add/remove/force/not_defined # Add or remove newline before the ')' in a function definition. nl_func_def_end = remove # ignore/add/remove/force/not_defined @@ -1991,7 +1991,7 @@ nl_func_call_empty = ignore # ignore/add/remove/force/not_defined nl_func_call_start = remove # ignore/add/remove/force/not_defined # Whether to add a newline before ')' in a function call. -nl_func_call_end = ignore # ignore/add/remove/force/not_defined +nl_func_call_end = remove # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. @@ -3515,5 +3515,5 @@ set QUESTION REAL_FATTR_CONST set QUESTION REAL_FATTR_NONNULL_ALL set QUESTION REAL_FATTR_PURE set QUESTION REAL_FATTR_WARN_UNUSED_RESULT -# option(s) with 'not default' value: 102 +# option(s) with 'not default' value: 112 # diff --git a/test/benchmark/bench_re_freeze_spec.lua b/test/benchmark/bench_regexp_spec.lua index ea41953014..903af5f574 100644 --- a/test/benchmark/bench_re_freeze_spec.lua +++ b/test/benchmark/bench_regexp_spec.lua @@ -1,4 +1,4 @@ --- Test for benchmarking RE engine. +-- Test for benchmarking the RE engine. local helpers = require('test.functional.helpers')(after_each) local insert, source = helpers.insert, helpers.source @@ -7,19 +7,19 @@ local clear, command = helpers.clear, helpers.command -- Temporary file for gathering benchmarking results for each regexp engine. local result_file = 'benchmark.out' -- Fixture containing an HTML fragment that can make a search appear to freeze. -local sample_file = 'test/benchmark/samples/re.freeze.txt' +local sample_file = 'src/nvim/testdir/samples/re.freeze.txt' -- Vim script code that does both the work and the benchmarking of that work. local measure_cmd = [[call Measure(%d, ']] .. sample_file .. [[', '\s\+\%%#\@<!$', '+5')]] local measure_script = [[ - func! Measure(re, file, pattern, arg) - let sstart=reltime() + func Measure(re, file, pattern, arg) + let sstart = reltime() - execute 'set re=' . a:re + execute 'set re=' .. a:re execute 'split' a:arg a:file call search(a:pattern, '', '', 10000) - q! + quit! $put =printf('file: %s, re: %d, time: %s', a:file, a:re, reltimestr(reltime(sstart))) endfunc]] diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 30c351b26a..5be4425162 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -1066,7 +1066,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) end) - it("does not crash when setting keymap in a non-existing buffer #13541", function() + it("does not crash when setting mapping in a non-existing buffer #13541", function() pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) helpers.assert_alive() end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index 07774866a8..795f639dad 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -1,3 +1,4 @@ +local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local assert_log = helpers.assert_log @@ -5,6 +6,7 @@ local assert_nolog = helpers.assert_nolog local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local ok = helpers.ok local feed = helpers.feed local funcs = helpers.funcs @@ -132,6 +134,57 @@ describe('fileio', function() eq('foo', foo_contents); end) + it('backup symlinked files #11349', function() + if isCI('cirrus') then + pending('FIXME: cirrus') + end + clear() + + local initial_content = 'foo' + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + command('set backup') + command('set backupcopy=yes') + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. "to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + + + it('backup symlinked files in first avialable backupdir #11349', function() + if isCI('cirrus') then + pending('FIXME: cirrus') + end + clear() + + local initial_content = 'foo' + local backup_dir = 'Xtest_backupdir' + local sep = helpers.get_pathsep() + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = backup_dir .. sep .. link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + mkdir(backup_dir) + command('set backup') + command('set backupcopy=yes') + command('set backupdir=.__this_does_not_exist__,' .. backup_dir) + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. " to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + it('readfile() on multibyte filename #10586', function() clear() local text = { diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index a32c801c97..2084d365a5 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -637,7 +637,7 @@ describe('runtime:', function() end) it('loads plugin/*.lua from start packages', function() - local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'catagory', + local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin'}, pathsep) local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 2440867c6e..f300fea3a0 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -330,7 +330,7 @@ describe('named marks view', function() os.remove(file2) end) - it('is restored', function() + it('is restored in normal mode but not op-pending mode', function() local screen = Screen.new(5, 8) screen:attach() command("edit " .. file1) @@ -358,6 +358,18 @@ describe('named marks view', function() 8 line | | ]]) + -- not in op-pending mode #20886 + feed("ggj=`a") + screen:expect([[ + 1 line | + ^2 line | + 3 line | + 4 line | + 5 line | + 6 line | + 7 line | + | + ]]) end) it('is restored across files', function() diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index f8ca6986bd..a7f629a76b 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -144,4 +144,10 @@ describe('tabpage', function() command(' silent :keepalt :: ::: silent! -2 tabmove') eq(1, funcs.nvim_tabpage_get_number(0)) end) + + it(':tabs does not overflow IObuff with long path with comma #20850', function() + meths.buf_set_name(0, ('x'):rep(1024) .. ',' .. ('x'):rep(1024)) + command('tabs') + assert_alive() + end) end) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua index e6f67ef18e..000e746f1c 100644 --- a/test/functional/ex_cmds/verbose_spec.lua +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -77,7 +77,7 @@ nohlsearch script_location), result) end) - it('"Last set" for keymap set by Lua', function() + it('"Last set" for mapping set by Lua', function() local result = exec_capture(':verbose map <leader>key1') eq(string.format([[ @@ -86,7 +86,7 @@ n \key1 * :echo "test"<CR> script_location), result) end) - it('"Last set" for keymap set by vim.keymap', function() + it('"Last set" for mapping set by vim.keymap', function() local result = exec_capture(':verbose map <leader>key2') eq(string.format([[ diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua new file mode 100644 index 0000000000..d7779684a4 --- /dev/null +++ b/test/functional/legacy/breakindent_spec.lua @@ -0,0 +1,44 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('breakindent', function() + -- oldtest: Test_cursor_position_with_showbreak() + it('cursor shown at correct position with showbreak', function() + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- SignColumn + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + exec([[ + let &signcolumn = 'yes' + let &showbreak = '+' + let leftcol = win_getid()->getwininfo()->get(0, {})->get('textoff') + eval repeat('x', &columns - leftcol - 1)->setline(1) + eval 'second line'->setline(2) + ]]) + screen:expect([[ + {1: }^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + {1: }second line | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('AX') + screen:expect([[ + {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {1: }^second line | + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]]) + end) +end) diff --git a/test/functional/legacy/digraph_spec.lua b/test/functional/legacy/digraph_spec.lua new file mode 100644 index 0000000000..0cb0bb84be --- /dev/null +++ b/test/functional/legacy/digraph_spec.lua @@ -0,0 +1,46 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed + +before_each(clear) + +describe('digraph', function() + -- oldtest: Test_entering_digraph() + it('characters displayed on the screen', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('i<C-K>') + screen:expect([[ + {1:^?} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('1') + screen:expect([[ + {1:^1} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('2') + screen:expect([[ + ½^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + end) +end) diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index 7fc5f11a79..362d33a0fd 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local expect = helpers.expect @@ -7,20 +8,51 @@ local sleep = helpers.sleep before_each(clear) --- oldtest: Test_autoindent_remove_indent() -it('autoindent removes indent when Insert mode is stopped', function() - command('set autoindent') - -- leaving insert mode in a new line with indent added by autoindent, should - -- remove the indent. - feed('i<Tab>foo<CR><Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - -- when a line is wrapped and the cursor is at the start of the second line, - -- leaving insert mode, should move the cursor back to the first line. - feed('o' .. ('x'):rep(20) .. '<Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - expect('\tfoo\n\n' .. ('x'):rep(20)) +describe('edit', function() + -- oldtest: Test_autoindent_remove_indent() + it('autoindent removes indent when Insert mode is stopped', function() + command('set autoindent') + -- leaving insert mode in a new line with indent added by autoindent, should + -- remove the indent. + feed('i<Tab>foo<CR><Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + -- when a line is wrapped and the cursor is at the start of the second line, + -- leaving insert mode, should move the cursor back to the first line. + feed('o' .. ('x'):rep(20) .. '<Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + expect('\tfoo\n\n' .. ('x'):rep(20)) + end) + + -- oldtest: Test_edit_insert_reg() + it('inserting a register using CTRL-R', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('a<C-R>') + screen:expect([[ + {1:^"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('=') + screen:expect([[ + {1:"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + =^ | + ]]) + end) end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 05d853622e..b5e45a86c1 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -639,7 +639,7 @@ describe('eval', function() end) it('function name includes a colon', function() - eq('Vim(function):E128: Function name must start with a capital or "s:": b:test()\\nendfunction', + eq('Vim(function):E884: Function name cannot contain a colon: b:test()\\nendfunction', exc_exec(dedent([[ function! b:test() endfunction]]))) diff --git a/test/functional/legacy/increment_spec.lua b/test/functional/legacy/increment_spec.lua index d51f9a2e02..d35f4bdae6 100644 --- a/test/functional/legacy/increment_spec.lua +++ b/test/functional/legacy/increment_spec.lua @@ -285,7 +285,7 @@ describe('Ctrl-A/Ctrl-X on visual selections', function() " 1 " 1 " 1 - " Expexted: + " Expected: " 1) g Ctrl-A on block selected indented lines " 2 " 1 diff --git a/test/functional/legacy/vimscript_spec.lua b/test/functional/legacy/vimscript_spec.lua new file mode 100644 index 0000000000..f59a87f824 --- /dev/null +++ b/test/functional/legacy/vimscript_spec.lua @@ -0,0 +1,90 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed +local meths = helpers.meths + +before_each(clear) + +describe('Vim script', function() + -- oldtest: Test_deep_nest() + it('Error when if/for/while/try/function is nested too deep',function() + local screen = Screen.new(80, 24) + screen:attach() + meths.set_option('laststatus', 2) + exec([[ + " Deep nesting of if ... endif + func Test1() + let @a = join(repeat(['if v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endif'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of for ... endfor + func Test2() + let @a = join(repeat(['for i in [1]'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endfor'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of while ... endwhile + func Test3() + let @a = join(repeat(['while v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endwhile'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of try ... endtry + func Test4() + let @a = join(repeat(['try'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endtry'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of function ... endfunction + func Test5() + let @a = join(repeat(['function X()'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endfunction'], 51), "\n") + @a + let @a = '' + endfunc + ]]) + screen:expect({any = '%[No Name%]'}) + feed(':call Test1()<CR>') + screen:expect({any = 'E579: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test2()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test3()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test4()<CR>') + screen:expect({any = 'E601: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test5()<CR>') + screen:expect({any = 'E1058: '}) + end) + + -- oldtest: Test_typed_script_var() + it('using s: with a typed command', function() + local screen = Screen.new(80, 24) + screen:attach() + feed(":echo get(s:, 'foo', 'x')\n") + screen:expect({any = 'E116: '}) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index f250a3ec93..38f772c5cf 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -430,6 +430,8 @@ describe('lua stdlib', function() it('vim.pesc', function() eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]])) eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) + -- pesc() returns one result. #20751 + eq({'x'}, exec_lua([[return {vim.pesc('x')}]])) -- Validates args. matches('s: expected string, got number', @@ -1016,11 +1018,11 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") eq(5120.1, funcs.luaeval "vim.g.floaty") - eq(NIL, funcs.luaeval "vim.g.nonexistant") + eq(NIL, funcs.luaeval "vim.g.nonexistent") eq(NIL, funcs.luaeval "vim.g.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL} + return {vim.g.nonexistent == vim.NIL, vim.g.nullvar == vim.NIL} ]]) eq({hello="world"}, funcs.luaeval "vim.g.to_delete") @@ -1123,12 +1125,12 @@ describe('lua stdlib', function() eq('bye', funcs.luaeval "vim.b[BUF].testing") eq(123, funcs.luaeval "vim.b.other") eq(5120.1, funcs.luaeval "vim.b.floaty") - eq(NIL, funcs.luaeval "vim.b.nonexistant") - eq(NIL, funcs.luaeval "vim.b[BUF].nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") + eq(NIL, funcs.luaeval "vim.b[BUF].nonexistent") eq(NIL, funcs.luaeval "vim.b.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} + return {vim.b.nonexistent == vim.NIL, vim.b.nullvar == vim.NIL} ]]) matches([[attempt to index .* nil value]], @@ -1207,7 +1209,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.testing") eq(NIL, funcs.luaeval "vim.b.other") - eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") end) it('vim.w', function() @@ -1226,8 +1228,8 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.w.testing") eq('bye', funcs.luaeval "vim.w[WIN].testing") eq(123, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") - eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") + eq(NIL, funcs.luaeval "vim.w[WIN].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) @@ -1305,7 +1307,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.testing") eq(NIL, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") end) it('vim.t', function() @@ -1317,10 +1319,10 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.t.testing") eq(123, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") eq('hi', funcs.luaeval "vim.t[0].testing") eq(123, funcs.luaeval "vim.t[0].other") - eq(NIL, funcs.luaeval "vim.t[0].nonexistant") + eq(NIL, funcs.luaeval "vim.t[0].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) @@ -1387,7 +1389,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.testing") eq(NIL, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") end) it('vim.env', function() diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua index 2c9b2d175e..5bff45a836 100644 --- a/test/functional/options/mousescroll_spec.lua +++ b/test/functional/options/mousescroll_spec.lua @@ -97,6 +97,24 @@ describe("'mousescroll'", function() eq(10, screencol()) scroll('left') eq(10, screencol()) + + -- vertical scrolling is still disabled with non-zero 'scrolloff' value + command('set scrolloff=1') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + -- also in insert mode + feed('i') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) end) local test_vertical_scrolling = function() diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index e5a60f5cb1..c970a03fcf 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -5,7 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local curbuf_contents = helpers.curbuf_contents local command = helpers.command -local eq, neq = helpers.eq, helpers.neq +local eq, neq, matches = helpers.eq, helpers.neq, helpers.matches local getcompletion = helpers.funcs.getcompletion describe(':checkhealth', function() @@ -29,8 +29,7 @@ describe(':checkhealth', function() -- Do this after startup, otherwise it just breaks $VIMRUNTIME. command("let $VIM='zub'") command("checkhealth nvim") - eq("ERROR: $VIM is invalid: zub", - string.match(curbuf_contents(), "ERROR: $VIM .* zub")) + matches('ERROR: $VIM .* zub', curbuf_contents()) end) it('completions can be listed via getcompletion()', function() clear() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 425427be54..a7094fff48 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2470,7 +2470,7 @@ describe('LSP', function() }, uri = "file:///test_a" }, - contanerName = "TestAContainer" + containerName = "TestAContainer" }, { deprecated = false, @@ -2489,7 +2489,7 @@ describe('LSP', function() }, uri = "file:///test_b" }, - contanerName = "TestBContainer" + containerName = "TestBContainer" } } return vim.lsp.util.symbols_to_items(sym_info, nil) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index e9072ebf98..5b62f5b3e1 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -1115,7 +1115,7 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} meths.buf_set_extmark(0, ns, 5, 0, { - virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unneccesary?", "Comment"}} }; + virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unnecessary?", "Comment"}} }; }) -- TODO: what about the cursor?? screen:expect{grid=[[ @@ -1128,7 +1128,7 @@ if (h->n_buckets < new_n_buckets) { // expand if (kh_is_map && val_size) { | ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_| buckets * val_size); | - {5:^^ REVIEW:}{6: new_vals variable seems unneccesary?} | + {5:^^ REVIEW:}{6: new_vals variable seems unnecessary?} | h->vals_buf = new_vals; | | ]]} diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 36dc5addcd..dd35f47ca1 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -1073,6 +1073,182 @@ int main(int argc, char **argv) :e | ]]) end) + + describe('line matching diff algorithm', function() + setup(function() + local f1 = [[if __name__ == "__main__": + import sys + app = QWidgets.QApplication(sys.args) + MainWindow = QtWidgets.QMainWindow() + ui = UI_MainWindow() + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname, f1, false) + local f2 = [[if __name__ == "__main__": + import sys + comment these things + #app = QWidgets.QApplication(sys.args) + #MainWindow = QtWidgets.QMainWindow() + add a completely different line here + #ui = UI_MainWindow() + add another new line + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname_2, f2, false) + end) + + it('diffopt+=linematch:20', function() + reread() + feed(':set diffopt=internal,filler<cr>') + screen:expect([[ + {1: }^if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{9: }{8:app = QWidgets}│{1: }{9: }{8:comment these}| + {1: }{9: }{8:MainWindow = Q}│{1: }{9: }{8:#app = QWidge}| + {1: }{9: }{8:ui = UI_}{9:MainWi}│{1: }{9: }{8:#MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{2:------------------}│{1: }{4: #ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt=internal,filler | + ]]) + + feed('G') + feed(':set diffopt+=linematch:20<cr>') + screen:expect([[ + {1: }if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{2:------------------}│{1: }{4: comment these}| + {1: }{9: app = QWidgets}│{1: }{9: }{8:#}{9:app = QWidge}| + {1: }{9: MainWindow = Q}│{1: }{9: }{8:#}{9:MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{9: ui = UI_MainWi}│{1: }{9: }{8:#}{9:ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } ^sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=linematch:20 | + ]]) + end) + end) + + describe('line matching diff algorithm with icase', function() + setup(function() + local f1 = [[DDD +_aa]] + write_file(fname, f1, false) + local f2 = [[DDD +AAA +ccca]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,icase', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{2:------------------}│{1: }{4:AAA }| + {1: }{8:_a}{9:a }│{1: }{8:ccc}{9:a }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]) + feed(':set diffopt+=icase<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{8:_}{9:aa }│{1: }{8:A}{9:AA }| + {1: }{2:------------------}│{1: }{4:ccca }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=icase | + ]]) + end) + end) + + describe('line matching diff algorithm with iwhiteall', function() + setup(function() + local f1 = [[BB + AAA]] + write_file(fname, f1, false) + local f2 = [[BB + AAB +AAAB]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,iwhiteall', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{9: AA}{8:A}{9: }│{1: }{9: AA}{8:B}{9: }| + {1: }{2:------------------}│{1: }{4:AAAB }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]} + feed(':set diffopt+=iwhiteall<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{2:------------------}│{1: }{4: AAB }| + {1: }{9: AAA }│{1: }{9:AAA}{8:B}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=iwhiteall | + ]]} + end) + end) end) it('win_update redraws lines properly', function() diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua new file mode 100644 index 0000000000..5601908929 --- /dev/null +++ b/test/functional/ui/linematch_spec.lua @@ -0,0 +1,981 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local feed = helpers.feed +local clear = helpers.clear +local write_file = helpers.write_file + +describe('Diff mode screen with 3 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local fname_3 = fname .. '.3' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + before_each(function() + clear() + feed(':set diffopt+=linematch:30<cr>') + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':vnew ' .. fname_3 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 16) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; +}) + feed('<c-w>=') + feed(':windo set nu!<cr>') + + + end) + describe('setup the diff screen to look like a merge conflict with 3 files in diff mode', function() + before_each(function() + + local f1 = [[ + + common line + AAA + AAA + AAA + ]] + local f2 = [[ + + common line + <<<<<<< HEAD + AAA + AAA + AAA + ======= + BBB + BBB + BBB + >>>>>>> branch1 + ]] + local f3 = [[ + + common line + BBB + BBB + BBB + ]] + + write_file(fname, f1, false) + write_file(fname_2, f2, false) + write_file(fname_3, f3, false) + reread() + end) + + it('get from window 1', function() + feed('1<c-w>w') + feed(':2,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 } AAA │{1: }{10: 4 } AAA │{1: }{10: 3 } AAA | + {1: }{10: 5 } AAA │{1: }{10: 5 } AAA │{1: }{10: 4 } AAA | + {1: }{10: 6 } AAA │{1: }{10: 6 } AAA │{1: }{10: 5 } AAA | + {1: }{10: 7 }{9:======= }│{1: }{10: 7 }{9:======= }│{1: }{10: }{2:---------------------------}| + {1: }{10: 8 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 9 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 10 }{9: BBB }│{1: }{10: 10 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 12 } │{1: }{10: 12 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {7:<-functional-diff-screen-1.3 [+] }{3:<est-functional-diff-screen-1.2 Xtest-functional-diff-screen-1 }| + :2,6diffget screen-1.2 | + ]]) + end) + + it('get from window 2', function() + feed('2<c-w>w') + feed(':5,7diffget screen-1.3<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 5 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: }{8:BBB}{9: }│{1: }{10: 6 }{9: }{8:BBB}{9: }│{1: }{10: 4 }{9: }{8:AAA}{9: }| + {1: }{10: 5 }{9: }{8:BBB}{9: }│{1: }{10: 7 }{9: }{8:BBB}{9: }│{1: }{10: 5 }{9: }{8:AAA}{9: }| + {1: }{10: }{2:---------------------------}│{1: }{10: 8 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 9 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,7diffget screen-1.3 | + ]]) + end) + + it('get from window 3', function() + feed('3<c-w>w') + feed(':5,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 <est-functional-diff-screen-1.2 }{7:<st-functional-diff-screen-1 [+] }| + :5,6diffget screen-1.2 | + ]]) + end) + + it('put from window 2 - part', function() + feed('2<c-w>w') + feed(':6,8diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 7 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 8 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,8diffput screen-1 | + ]]) + + end) + it('put from window 2 - part to end', function() + feed('2<c-w>w') + feed(':6,11diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,11diffput screen-1 | + ]]) + + end) + end) +end) + +describe('Diff mode screen with 2 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + end) + + before_each(function() + clear() + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 20) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; +}) + feed('<c-w>=') + feed(':windo set nu!<cr>') + + + end) + describe('setup a diff with 2 files and set linematch:30', function() + before_each(function() + feed(':set diffopt+=linematch:30<cr>') + local f1 = [[ + +common line +common line + +DEFabc +xyz +xyz +xyz +DEFabc +DEFabc +DEFabc +common line +common line +DEF +common line +DEF +something + ]] + local f2 = [[ + +common line +common line + +ABCabc +ABCabc +ABCabc +ABCabc +common line +common line +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('get from window 1 from line 5 to 9', function() + feed('1<c-w>w') + feed(':5,9diffget<cr>') + screen:expect([[ + {1:+ }{10: 1 }{5:^+-- 7 lines: common line··················}│{1:+ }{10: 1 }{5:+-- 7 lines: common line···················}| + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,9diffget | + ]]) + end) + it('get from window 2 from line 5 to 10', function() + feed('2<c-w>w') + feed(':5,10diffget<cr>') + screen:expect([[ + {1:- }{10: 1 } │{1:- }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 8 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 11 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 12 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 13 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 14 }something | + {1: }{10: 13 } │{1: }{10: 15 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :5,10diffget | + ]]) + end) + it('get all from window 2', function() + feed('2<c-w>w') + feed(':4,17diffget<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 8 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }common line │{1: }{10: 11 }common line | + {1: }{10: 12 }something │{1: }{10: 12 }something | + {1: }{10: 13 } │{1: }{10: 13 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :4,17diffget | + ]]) + + end) + it('get all from window 1', function() + feed('1<c-w>w') + feed(':4,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }common line │{1: }{10: 15 }common line | + {1: }{10: 16 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 17 }something │{1: }{10: 17 }something | + {1: }{10: 18 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :4,12diffget | + ]]) + end) + it('get from window 1 using do 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }^DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 12 }^common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 13 }something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }^something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 1 using dp 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }^ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }^common line │{1: }{10: 14 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 15 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }^something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 2 using dp line 6', function() + feed('2<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }^xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 8', function() + feed('2<c-w>w') + feed('8gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }^xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 9', function() + feed('2<c-w>w') + feed('9gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }^DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 17', function() + feed('2<c-w>w') + feed('17gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }something │{1: }{10: 17 }^something | + {1: }{10: 14 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + + end) + end) + describe('setup a diff with 2 files and set linematch:10', function() + before_each(function() + feed(':set diffopt+=linematch:10<cr>') + local f1 = [[ +common line +HIL + +aABCabc +aABCabc +aABCabc +aABCabc +common line +HIL +common line +something + ]] + local f2 = [[ +common line +DEF +GHI +something + +aDEFabc +xyz +xyz +xyz +aDEFabc +aDEFabc +aDEFabc +common line +DEF +GHI +something else +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('enable linematch for the longest diff block by increasing the number argument passed to linematch', function() + feed('1<c-w>w') + -- linematch is disabled for the longest diff because it's combined line length is over 10 + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + -- enable it by increasing the number + feed(":set diffopt-=linematch:10<cr>") + feed(":set diffopt+=linematch:30<cr>") + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 8 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 9 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 10 }{9:a}{8:DEF}{9:abc }│{1: }{10: 5 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 11 }{9:a}{8:DEF}{9:abc }│{1: }{10: 6 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 12 }{9:a}{8:DEF}{9:abc }│{1: }{10: 7 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :set diffopt+=linematch:30 | + ]]) + end) + it('get all from second window', function() + feed('2<c-w>w') + feed(':1,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }DEF │{1: }{10: 2 }DEF | + {1: }{10: 3 }GHI │{1: }{10: 3 }GHI | + {1: }{10: 4 }something │{1: }{10: 4 }something | + {1: }{10: 5 } │{1: }{10: 5 } | + {1: }{10: 6 }aDEFabc │{1: }{10: 6 }aDEFabc | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }xyz │{1: }{10: 9 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 11 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 12 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 15 }GHI | + {1: }{10: 16 }something else │{1: }{10: 16 }something else | + {1: }{10: 17 }common line │{1: }{10: 17 }common line | + {1: }{10: 18 }something │{1: }{10: 18 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :1,12diffget | + ]]) + end) + it('get all from first window', function() + feed('1<c-w>w') + feed(':1,19diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }HIL │{1: }{10: 2 }HIL | + {1: }{10: 3 } │{1: }{10: 3 } | + {1: }{10: 4 }aABCabc │{1: }{10: 4 }aABCabc | + {1: }{10: 5 }aABCabc │{1: }{10: 5 }aABCabc | + {1: }{10: 6 }aABCabc │{1: }{10: 6 }aABCabc | + {1: }{10: 7 }aABCabc │{1: }{10: 7 }aABCabc | + {1: }{10: 8 }common line │{1: }{10: 8 }common line | + {1: }{10: 9 }HIL │{1: }{10: 9 }HIL | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }something │{1: }{10: 11 }something | + {1: }{10: 12 } │{1: }{10: 12 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :1,19diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 7 - 8 (non line matched block)', function() + feed('2<c-w>w') + feed(':7,8diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 8 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 9 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 11 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 12 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 13 }common line | + {1: }{10: 18 }something │{1: }{10: 14 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :7,8diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 8 - 10 (line matched block)', function() + feed('2<c-w>w') + feed(':8,10diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }DEF │{1: }{10: 9 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 10 }GHI | + {1: }{10: 16 }something else │{1: }{10: 11 }something else | + {1: }{10: 17 }common line │{1: }{10: 12 }common line | + {1: }{10: 18 }something │{1: }{10: 13 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :8,10diffget | + ]]) + end) + end) +end) diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index f111aa2513..5aacdc95e2 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -894,6 +894,31 @@ local function screen_tests(linegrid) :ls^ | ]]) end) + + it('VimResized autocommand does not cause invalid UI events #20692 #20759', function() + feed('<Esc>') + command([[autocmd VimResized * redrawtabline]]) + command([[autocmd VimResized * lua vim.api.nvim_echo({ { 'Hello' } }, false, {})]]) + command([[autocmd VimResized * let g:echospace = v:echospace]]) + meths.set_option('showtabline', 2) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + | + ]]) + screen:try_resize(30, 6) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + eq(29, meths.get_var('echospace')) + end) end) describe('press enter', function() diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index ff3e143126..a1683a32c9 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -158,7 +158,7 @@ describe('Signs', function() ]]) end) - it('higlights the cursorline sign with culhl', function() + it('highlights the cursorline sign with culhl', function() feed('ia<cr>b<cr>c<esc>') command('sign define piet text=>> texthl=Search culhl=ErrorMsg') command('sign place 1 line=1 name=piet buffer=1') diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index 809486d4db..0e35a03557 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -84,3 +84,39 @@ describe('ui/ext_tabline', function() end} end) end) + +describe("tabline", function() + local screen + + before_each(function() + clear() + screen = Screen.new(42, 5) + screen:attach() + end) + + it('redraws when tabline option is set', function() + command('set tabline=asdf') + command('set showtabline=2') + screen:expect{grid=[[ + {1:asdf }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + }} + command('set tabline=jkl') + screen:expect{grid=[[ + {1:jkl }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue}; + }} + end) +end) diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index 1fbdedb815..9b0ace0882 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -16,13 +16,16 @@ local lfs = require('lfs') local clear = helpers.clear local eq = helpers.eq local exc_exec = helpers.exc_exec -local exec = helpers.exec +local exec_lua = helpers.exec_lua local exec_capture = helpers.exec_capture local eval = helpers.eval local command = helpers.command local write_file = helpers.write_file local meths = helpers.meths local sleep = helpers.sleep +local matches = helpers.matches +local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive local poke_eventloop = helpers.poke_eventloop local feed = helpers.feed @@ -148,79 +151,6 @@ describe('List support code', function() end) end) --- oldtest: Test_deep_nest() -it('Error when if/for/while/try/function is nested too deep',function() - clear() - local screen = Screen.new(80, 24) - screen:attach() - meths.set_option('laststatus', 2) - exec([[ - " Deep nesting of if ... endif - func Test1() - let @a = join(repeat(['if v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endif'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of for ... endfor - func Test2() - let @a = join(repeat(['for i in [1]'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endfor'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of while ... endwhile - func Test3() - let @a = join(repeat(['while v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endwhile'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of try ... endtry - func Test4() - let @a = join(repeat(['try'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endtry'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of function ... endfunction - func Test5() - let @a = join(repeat(['function X()'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endfunction'], 51), "\n") - @a - let @a = '' - endfunc - ]]) - screen:expect({any = '%[No Name%]'}) - feed(':call Test1()<CR>') - screen:expect({any = 'E579: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test2()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test3()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test4()<CR>') - screen:expect({any = 'E601: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test5()<CR>') - screen:expect({any = 'E1058: '}) -end) - describe("uncaught exception", function() before_each(clear) after_each(function() @@ -249,10 +179,10 @@ describe("uncaught exception", function() end) end) -describe('lambda function', function() +describe('listing functions using :function', function() before_each(clear) - it('can be shown using :function followed by <lambda> #20466', function() + it('works for lambda functions with <lambda> #20466', function() command('let A = {-> 1}') local num = exec_capture('echo A'):match("function%('<lambda>(%d+)'%)") eq(([[ @@ -260,4 +190,33 @@ describe('lambda function', function() 1 return 1 endfunction]]):format(num), exec_capture(('function <lambda>%s'):format(num))) end) + + -- FIXME: If the same function is deleted, the crash still happens. #20790 + it('does not crash if another function is deleted while listing', function() + local screen = Screen.new(80, 24) + screen:attach() + matches('.*: Vim%(function%):E454: function list was modified', pcall_err(exec_lua, [=[ + vim.cmd([[ + func Func1() + endfunc + func Func2() + endfunc + func Func3() + endfunc + ]]) + + local ns = vim.api.nvim_create_namespace('test') + + vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) + if event == 'msg_show' and content[1][2] == 'function Func1()' then + vim.cmd('delfunc Func3') + end + end) + + vim.cmd('function') + + vim.ui_detach(ns) + ]=])) + assert_alive() + end) end) diff --git a/test/functional/vimscript/exepath_spec.lua b/test/functional/vimscript/exepath_spec.lua index 439dd96fcd..db96b79743 100644 --- a/test/functional/vimscript/exepath_spec.lua +++ b/test/functional/vimscript/exepath_spec.lua @@ -23,7 +23,7 @@ describe('exepath()', function() for _, input in ipairs({'v:null', 'v:true', 'v:false', '{}', '[]'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) end - eq('Vim(call):E1142: Non-empty string required for argument 1', exc_exec('call exepath("")')) + eq('Vim(call):E1175: Non-empty string required for argument 1', exc_exec('call exepath("")')) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 2451da983e..1153baac46 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -69,7 +69,7 @@ describe('NULL', function() null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) - null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() + null_expr_test('does not crash append()', 'append(0, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function() diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 6387f89fe4..34dbf592a5 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2253,8 +2253,8 @@ describe('typval.c', function() local d1 = dict() alloc_log:check({a.dict(d1)}) eq(1, d1.dv_refcount) - eq(false, tv_dict_equal(nil, d1)) - eq(false, tv_dict_equal(d1, nil)) + eq(true, tv_dict_equal(nil, d1)) + eq(true, tv_dict_equal(d1, nil)) eq(true, tv_dict_equal(d1, d1)) eq(1, d1.dv_refcount) alloc_log:check({}) @@ -2721,8 +2721,8 @@ describe('typval.c', function() local d1 = lua2typvalt({}) alloc_log:check({a.dict(d1.vval.v_dict)}) eq(1, d1.vval.v_dict.dv_refcount) - eq(false, tv_equal(nd, d1)) - eq(false, tv_equal(d1, nd)) + eq(true, tv_equal(nd, d1)) + eq(true, tv_equal(d1, nd)) eq(true, tv_equal(d1, d1)) eq(1, d1.vval.v_dict.dv_refcount) alloc_log:check({}) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index eb23a3cff1..3ed3a4aec9 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -504,6 +504,16 @@ describe('path.c', function() eq(OK, result) end) + itp('does not remove trailing slash from non-existing relative directory #20847', function() + local expected = lfs.currentdir() .. '/non_existing_dir/' + local filename = 'non_existing_dir/' + local buflen = get_buf_len(expected, filename) + local do_expand = 1 + local buf, result = vim_FullName(filename, buflen, do_expand) + eq(expected, ffi.string(buf)) + eq(OK, result) + end) + itp('expands "./" to the current directory #7117', function() local expected = lfs.currentdir() .. '/unit-test-directory/test.file' local filename = './unit-test-directory/test.file' |