aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/labeler.yml5
-rw-r--r--.github/scripts/reviews.js88
-rw-r--r--.github/workflows/ci.yml181
-rwxr-xr-x.github/workflows/env.sh2
-rw-r--r--.github/workflows/labeler.yml23
-rw-r--r--.github/workflows/reviews.yml18
-rwxr-xr-xci/before_cache.sh4
-rw-r--r--ci/common/build.sh7
-rwxr-xr-xci/common/submit_coverage.sh2
-rw-r--r--ci/common/suite.sh46
-rw-r--r--ci/common/test.sh10
-rwxr-xr-xci/run_lint.sh36
-rwxr-xr-xci/run_tests.sh57
-rw-r--r--runtime/autoload/freebasic.vim3
-rw-r--r--runtime/autoload/health.vim9
-rw-r--r--runtime/doc/api.txt54
-rw-r--r--runtime/doc/autocmd.txt15
-rw-r--r--runtime/doc/builtin.txt262
-rw-r--r--runtime/doc/change.txt7
-rw-r--r--runtime/doc/editing.txt11
-rw-r--r--runtime/doc/eval.txt5
-rw-r--r--runtime/doc/index.txt4
-rw-r--r--runtime/doc/lsp.txt4
-rw-r--r--runtime/doc/lua.txt55
-rw-r--r--runtime/doc/map.txt4
-rw-r--r--runtime/doc/options.txt8
-rw-r--r--runtime/doc/pattern.txt33
-rw-r--r--runtime/doc/quickfix.txt20
-rw-r--r--runtime/doc/repeat.txt21
-rw-r--r--runtime/doc/treesitter.txt132
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/doc/various.txt2
-rw-r--r--runtime/doc/vim_diff.txt9
-rw-r--r--runtime/doc/visual.txt1
-rw-r--r--runtime/filetype.vim17
-rw-r--r--runtime/lua/vim/diagnostic.lua5
-rw-r--r--runtime/lua/vim/filetype.lua6
-rw-r--r--runtime/lua/vim/highlight.lua105
-rw-r--r--runtime/lua/vim/lsp.lua2
-rw-r--r--runtime/lua/vim/lsp/handlers.lua4
-rw-r--r--runtime/lua/vim/lsp/util.lua4
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua18
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua28
-rw-r--r--runtime/lua/vim/treesitter/query.lua16
-rw-r--r--runtime/lua/vim/ui.lua2
-rw-r--r--runtime/syntax/structurizr.vim9
-rw-r--r--scripts/lintcommit.lua26
-rwxr-xr-xscripts/vim-patch.sh4
-rw-r--r--src/nvim/api/buffer.c119
-rw-r--r--src/nvim/api/keysets.lua4
-rw-r--r--src/nvim/api/private/helpers.c52
-rw-r--r--src/nvim/api/vim.c34
-rw-r--r--src/nvim/api/window.c2
-rw-r--r--src/nvim/auevents.lua5
-rw-r--r--src/nvim/autocmd.c20
-rw-r--r--src/nvim/buffer.c88
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/change.c15
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/charset.c4
-rw-r--r--src/nvim/eval.c45
-rw-r--r--src/nvim/eval.lua7
-rw-r--r--src/nvim/eval/funcs.c161
-rw-r--r--src/nvim/ex_cmds.c18
-rw-r--r--src/nvim/ex_cmds2.c9
-rw-r--r--src/nvim/ex_docmd.c72
-rw-r--r--src/nvim/ex_getln.c2
-rw-r--r--src/nvim/file_search.c21
-rw-r--r--src/nvim/fileio.c105
-rw-r--r--src/nvim/getchar.c31
-rw-r--r--src/nvim/getchar.h2
-rw-r--r--src/nvim/globals.h5
-rw-r--r--src/nvim/highlight.c50
-rw-r--r--src/nvim/lua/executor.c24
-rw-r--r--src/nvim/lua/stdlib.c7
-rw-r--r--src/nvim/main.c6
-rw-r--r--src/nvim/mbyte.c47
-rw-r--r--src/nvim/memline.c4
-rw-r--r--src/nvim/message.c1
-rw-r--r--src/nvim/normal.c29
-rw-r--r--src/nvim/ops.c242
-rw-r--r--src/nvim/option.c106
-rw-r--r--src/nvim/path.c3
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c439
-rw-r--r--src/nvim/quickfix.h1
-rw-r--r--src/nvim/screen.c50
-rw-r--r--src/nvim/search.c530
-rw-r--r--src/nvim/search.h3
-rw-r--r--src/nvim/spellfile.c2
-rw-r--r--src/nvim/syntax.c62
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/terminal.c20
-rw-r--r--src/nvim/testdir/setup.vim1
-rw-r--r--src/nvim/testdir/test_assert.vim174
-rw-r--r--src/nvim/testdir/test_autochdir.vim10
-rw-r--r--src/nvim/testdir/test_autocmd.vim26
-rw-r--r--src/nvim/testdir/test_blockedit.vim48
-rw-r--r--src/nvim/testdir/test_breakindent.vim4
-rw-r--r--src/nvim/testdir/test_cdo.vim205
-rw-r--r--src/nvim/testdir/test_cindent.vim9
-rw-r--r--src/nvim/testdir/test_cursor_func.vim75
-rw-r--r--src/nvim/testdir/test_digraph.vim4
-rw-r--r--src/nvim/testdir/test_edit.vim2
-rw-r--r--src/nvim/testdir/test_ex_mode.vim5
-rw-r--r--src/nvim/testdir/test_excmd.vim14
-rw-r--r--src/nvim/testdir/test_filechanged.vim111
-rw-r--r--src/nvim/testdir/test_filetype.vim5
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim35
-rw-r--r--src/nvim/testdir/test_functions.vim326
-rw-r--r--src/nvim/testdir/test_global.vim30
-rw-r--r--src/nvim/testdir/test_help.vim12
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim43
-rw-r--r--src/nvim/testdir/test_highlight.vim25
-rw-r--r--src/nvim/testdir/test_listdict.vim43
-rw-r--r--src/nvim/testdir/test_matchfuzzy.vim248
-rw-r--r--src/nvim/testdir/test_normal.vim29
-rw-r--r--src/nvim/testdir/test_options.vim50
-rw-r--r--src/nvim/testdir/test_packadd.vim361
-rw-r--r--src/nvim/testdir/test_profile.vim31
-rw-r--r--src/nvim/testdir/test_put.vim44
-rw-r--r--src/nvim/testdir/test_quickfix.vim75
-rw-r--r--src/nvim/testdir/test_registers.vim2
-rw-r--r--src/nvim/testdir/test_retab.vim21
-rw-r--r--src/nvim/testdir/test_search.vim2
-rw-r--r--src/nvim/testdir/test_stat.vim42
-rw-r--r--src/nvim/testdir/test_substitute.vim55
-rw-r--r--src/nvim/testdir/test_suspend.vim4
-rw-r--r--src/nvim/testdir/test_syntax.vim50
-rw-r--r--src/nvim/testdir/test_system.vim26
-rw-r--r--src/nvim/testdir/test_tabline.vim11
-rw-r--r--src/nvim/testdir/test_textformat.vim43
-rw-r--r--src/nvim/testdir/test_utf8.vim2
-rw-r--r--src/nvim/testdir/test_vimscript.vim5
-rw-r--r--src/nvim/testdir/test_visual.vim334
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim19
-rw-r--r--src/nvim/testdir/test_writefile.vim152
-rw-r--r--src/nvim/tui/input.c2
-rw-r--r--src/nvim/window.c88
-rw-r--r--test/functional/api/buffer_spec.lua35
-rw-r--r--test/functional/api/command_spec.lua22
-rw-r--r--test/functional/api/highlight_spec.lua54
-rw-r--r--test/functional/api/keymap_spec.lua18
-rw-r--r--test/functional/api/server_requests_spec.lua6
-rw-r--r--test/functional/api/vim_spec.lua47
-rw-r--r--test/functional/autocmd/dirchanged_spec.lua128
-rw-r--r--test/functional/core/job_spec.lua6
-rw-r--r--test/functional/editor/put_spec.lua12
-rw-r--r--test/functional/ex_cmds/ctrl_c_spec.lua3
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule_empty/health.lua7
-rw-r--r--test/functional/legacy/assert_spec.lua233
-rw-r--r--test/functional/legacy/cdo_spec.lua228
-rw-r--r--test/functional/legacy/ex_mode_spec.lua36
-rw-r--r--test/functional/legacy/excmd_spec.lua32
-rw-r--r--test/functional/legacy/filechanged_spec.lua131
-rw-r--r--test/functional/legacy/packadd_spec.lua507
-rw-r--r--test/functional/legacy/put_spec.lua45
-rw-r--r--test/functional/legacy/utf8_spec.lua2
-rw-r--r--test/functional/lua/vim_spec.lua17
-rw-r--r--test/functional/plugin/health_spec.lua14
-rw-r--r--test/functional/treesitter/highlight_spec.lua89
-rw-r--r--test/functional/treesitter/parser_spec.lua19
-rw-r--r--test/functional/ui/float_spec.lua22
-rw-r--r--test/functional/ui/highlight_spec.lua101
-rw-r--r--test/functional/ui/sign_spec.lua26
-rw-r--r--test/helpers.lua6
-rw-r--r--third-party/CMakeLists.txt4
167 files changed, 6207 insertions, 2388 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 52998c65cf..e20a2577cd 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -51,7 +51,7 @@
- "**/CMakeLists.txt"
- "**/*.cmake"
-"tests":
+"test":
- all: ["test/**/*"]
"ci":
@@ -59,3 +59,6 @@
- .github/workflows/**/*
- .builds/*
- ci/**/*
+
+"filetype":
+ - runtime/lua/vim/filetype.lua
diff --git a/.github/scripts/reviews.js b/.github/scripts/reviews.js
new file mode 100644
index 0000000000..25ef08be36
--- /dev/null
+++ b/.github/scripts/reviews.js
@@ -0,0 +1,88 @@
+module.exports = async ({github, context}) => {
+ const pr_data = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number
+ })
+ const labels = pr_data.data.labels.map(e => e.name)
+
+ const reviewers = new Set()
+ if (labels.includes('api')) {
+ reviewers.add("bfredl")
+ reviewers.add("gpanders")
+ reviewers.add("muniter")
+ }
+
+ if (labels.includes('ci')) {
+ reviewers.add("jamessan")
+ }
+
+ if (labels.includes('column')) {
+ reviewers.add("lewis6991")
+ }
+
+ if (labels.includes('diagnostic')) {
+ reviewers.add("gpanders")
+ }
+
+ if (labels.includes('diff')) {
+ reviewers.add("lewis6991")
+ }
+
+ if (labels.includes('distribution')) {
+ reviewers.add("jamessan")
+ }
+
+ if (labels.includes('documentation')) {
+ reviewers.add("clason")
+ }
+
+ if (labels.includes('extmarks')) {
+ reviewers.add("bfredl")
+ }
+
+ if (labels.includes('filetype')) {
+ reviewers.add("clason")
+ reviewers.add("gpanders")
+ }
+
+ if (labels.includes('gui')) {
+ reviewers.add("glacambre")
+ reviewers.add("smolck")
+ }
+
+ if (labels.includes('lsp')) {
+ reviewers.add("mfussenegger")
+ reviewers.add("mjlbach")
+ }
+
+ if (labels.includes('treesitter')) {
+ reviewers.add("bfredl")
+ reviewers.add("vigoux")
+ }
+
+ if (labels.includes('typo')) {
+ reviewers.add("dundargoc")
+ }
+
+ if (labels.includes('ui')) {
+ reviewers.add("bfredl")
+ }
+
+ if (labels.includes('vim-patch')) {
+ reviewers.add("janlazo")
+ reviewers.add("seandewar")
+ reviewers.add("zeertzjq")
+ }
+
+ // Remove person that opened the PR since they can't review themselves
+ const pr_opener = pr_data.data.user.login
+ reviewers.delete(pr_opener)
+
+ github.rest.pulls.requestReviewers({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ reviewers: Array.from(reviewers)
+ });
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b650c5dd85..9bc2e64475 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,6 +9,8 @@ on:
branches:
- 'master'
- 'release-[0-9]+.[0-9]+'
+ paths-ignore:
+ - 'runtime/doc/*'
# Cancel any in-progress CI runs for a PR if it is updated
concurrency:
@@ -29,29 +31,68 @@ jobs:
- name: Install apt packages
run: |
+ sudo add-apt-repository ppa:neovim-ppa/stable
sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip flake8
-
- - name: Setup interpreter packages
- run: |
- ./ci/install.sh
-
- - name: Cache dependencies
+ sudo apt-get install -y \
+ autoconf \
+ automake \
+ build-essential \
+ ccache \
+ cmake \
+ flake8 \
+ gettext \
+ gperf \
+ libluajit-5.1-dev \
+ libmsgpack-dev \
+ libtermkey-dev \
+ libtool-bin \
+ libtree-sitter-dev \
+ libunibilium-dev \
+ libuv1-dev \
+ libvterm-dev \
+ locales \
+ lua-busted \
+ lua-check \
+ lua-filesystem \
+ lua-inspect \
+ lua-lpeg \
+ lua-luv-dev \
+ lua-nvim \
+ luajit \
+ ninja-build \
+ pkg-config
+
+ - name: Cache artifacts
uses: actions/cache@v2
with:
path: |
- ${{ env.CACHE_NVIM_DEPS_DIR }}
~/.ccache
- key: ${{ matrix.runner }}-lint-${{ matrix.cc }}-${{ hashFiles('cmake/*', 'third-party/**', '**/CMakeLists.txt') }}-${{ github.base_ref }}
+ key: lint-${{ hashFiles('cmake/*', '**/CMakeLists.txt', '!third-party/**CMakeLists.txt') }}-${{ github.base_ref }}
- - name: Build third-party
- run: ./ci/before_script.sh
+ - name: Build nvim
+ run: ./ci/run_tests.sh build
+
+ - if: "!cancelled()"
+ name: clint
+ run: ./ci/run_lint.sh clint
+
+ - if: "!cancelled()"
+ name: lualint
+ run: ./ci/run_lint.sh lualint
- - name: Run lint
- run: ./ci/script.sh
+ - if: "!cancelled()"
+ name: pylint
+ run: ./ci/run_lint.sh pylint
+
+ - if: "!cancelled()"
+ name: shlint
+ run: ./ci/run_lint.sh shlint
+
+ - if: "!cancelled()"
+ name: single-includes
+ run: ./ci/run_lint.sh single-includes
- name: Cache dependencies
- if: ${{ success() }}
run: ./ci/before_cache.sh
unixish:
@@ -74,6 +115,11 @@ jobs:
- cc: clang
runner: macos-11.0
os: osx
+ - flavor: functionaltest-lua
+ cc: gcc
+ runner: ubuntu-20.04
+ os: linux
+ cmake: minimum_required
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
if: github.event.pull_request.draft == false
@@ -92,6 +138,24 @@ jobs:
sudo apt-get update
sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip
+ - name: Install minimum required version of cmake
+ if: matrix.cmake == 'minimum_required'
+ env:
+ CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
+ CMAKE_VERSION: '3.10.0'
+ shell: bash
+ run: |
+ curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
+ mkdir -p "$HOME/.local/bin" /opt/cmake-custom
+ chmod a+x /tmp/cmake-installer.sh
+ /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
+ ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
+ cmake_version="$(cmake --version | head -1)"
+ echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
+ echo "Unexpected CMake version: $cmake_version"
+ exit 1
+ }
+
- name: Install new clang
if: matrix.flavor == 'asan' || matrix.flavor == 'tsan'
run: |
@@ -107,8 +171,7 @@ jobs:
brew install automake ccache perl cpanminus ninja
- name: Setup interpreter packages
- run: |
- ./ci/install.sh
+ run: ./ci/install.sh
- name: Cache dependencies
uses: actions/cache@v2
@@ -121,11 +184,26 @@ jobs:
- name: Build third-party
run: ./ci/before_script.sh
- - name: Build and test
- run: ./ci/script.sh
+ - name: Build
+ run: ./ci/run_tests.sh build
+
+ - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && !cancelled()
+ name: Unittests
+ run: ./ci/run_tests.sh unittests
+
+ - if: matrix.flavor != 'tsan' && !cancelled()
+ name: Functionaltests
+ run: ./ci/run_tests.sh functionaltests
+
+ - if: "!cancelled()"
+ name: Oldtests
+ run: ./ci/run_tests.sh oldtests
+
+ - if: "!cancelled()"
+ name: Install nvim
+ run: ./ci/run_tests.sh install_nvim
- name: Cache dependencies
- if: ${{ success() }}
run: ./ci/before_cache.sh
windows:
@@ -152,68 +230,3 @@ jobs:
run: powershell ci\build.ps1
env:
CONFIGURATION: ${{ matrix.config }}
-
- functionaltest:
- name: ${{ matrix.runner }} ${{ matrix.flavor }} (cc=${{ matrix.cc }})
- strategy:
- fail-fast: false
- matrix:
- include:
- - flavor: functionaltest-lua
- cc: gcc
- runner: ubuntu-20.04
- os: linux
- runs-on: ${{ matrix.runner }}
- timeout-minutes: 45
- env:
- CC: ${{ matrix.cc }}
- CI_OS_NAME: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v2
-
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh ${{ matrix.flavor }}
-
- - name: Install apt packages
- run: |
- sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential ccache cmake cpanminus cscope gcc-multilib gdb gettext gperf language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip
-
- - name: Install minimum required version of cmake
- env:
- CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
- CMAKE_VERSION: '3.10.0'
- shell: bash
- run: |
- curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
- mkdir -p "$HOME/.local/bin" /opt/cmake-custom
- chmod a+x /tmp/cmake-installer.sh
- /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
- ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
- cmake_version="$(cmake --version | head -1)"
- echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
- echo "Unexpected CMake version: $cmake_version"
- exit 1
- }
-
- - name: Setup interpreter packages
- run: |
- ./ci/install.sh
-
- - name: Cache dependencies
- uses: actions/cache@v2
- with:
- path: |
- ${{ env.CACHE_NVIM_DEPS_DIR }}
- ~/.ccache
- key: ${{ matrix.runner }}-${{ matrix.flavor }}-${{ matrix.cc }}-${{ hashFiles('cmake/*', 'third-party/**', '**/CMakeLists.txt') }}-${{ github.base_ref }}
-
- - name: Build third-party
- run: ./ci/before_script.sh
-
- - name: Build and test
- run: ./ci/script.sh
-
- - name: Cache dependencies
- if: ${{ success() }}
- run: ./ci/before_cache.sh
diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh
index 0964995605..d424924c27 100755
--- a/.github/workflows/env.sh
+++ b/.github/workflows/env.sh
@@ -46,7 +46,9 @@ CLANG_SANITIZER=TSAN
EOF
;;
lint)
+ BUILD_FLAGS="$BUILD_FLAGS -DLIBLUV_LIBRARY:FILEPATH=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/lua/5.1/luv.so -DLIBLUV_INCLUDE_DIR:PATH=/usr/include/lua5.1"
cat <<EOF >> "$GITHUB_ENV"
+USE_BUNDLED=OFF
CI_TARGET=lint
EOF
;;
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 3de0c453a5..d50e0c1f92 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -13,10 +13,10 @@ jobs:
- uses: actions/labeler@main
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ sync-labels: ""
type-scope:
runs-on: ubuntu-latest
- needs: ["triage"]
permissions:
contents: write
pull-requests: write
@@ -27,8 +27,21 @@ jobs:
PR_TITLE: ${{ github.event.pull_request.title }}
steps:
- name: "Extract commit type and add as label"
- continue-on-error: true
- run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')"
+ run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" || true
- name: "Extract commit scope and add as label"
- continue-on-error: true
- run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')"
+ run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')" || true
+
+ request-reviewer:
+ if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ needs: ["triage", "type-scope"]
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v2
+ - name: 'Request reviewers'
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const script = require('./.github/scripts/reviews.js')
+ await script({github, context})
diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml
new file mode 100644
index 0000000000..964f57b871
--- /dev/null
+++ b/.github/workflows/reviews.yml
@@ -0,0 +1,18 @@
+name: "Request reviews"
+on:
+ pull_request_target:
+ types: [labeled, ready_for_review]
+jobs:
+ request-reviewer:
+ if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v2
+ - name: 'Request reviewers'
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const script = require('./.github/scripts/reviews.js')
+ await script({github, context})
diff --git a/ci/before_cache.sh b/ci/before_cache.sh
index c86109168e..bec6c37bbe 100755
--- a/ci/before_cache.sh
+++ b/ci/before_cache.sh
@@ -7,6 +7,8 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CI_DIR}/common/build.sh"
source "${CI_DIR}/common/suite.sh"
+mkdir -p "${HOME}/.cache"
+
echo "before_cache.sh: cache size"
du -chd 1 "${HOME}/.cache" | sort -rh | head -20
@@ -16,7 +18,7 @@ ccache -s 2>/dev/null || true
find "${HOME}/.ccache" -name stats -delete
# Update the third-party dependency cache only if the build was successful.
-if ended_successfully; then
+if ended_successfully && [ -d "${DEPS_BUILD_DIR}" ]; then
# Do not cache downloads. They should not be needed with up-to-date deps.
rm -rf "${DEPS_BUILD_DIR}/build/downloads"
rm -rf "${CACHE_NVIM_DEPS_DIR}"
diff --git a/ci/common/build.sh b/ci/common/build.sh
index c44c234274..b8bbff0b16 100644
--- a/ci/common/build.sh
+++ b/ci/common/build.sh
@@ -44,7 +44,9 @@ build_deps() {
cd "${CI_BUILD_DIR}"
}
-prepare_build() {
+build_nvim() {
+ check_core_dumps --delete quiet
+
if test -n "${CLANG_SANITIZER}" ; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DCLANG_${CLANG_SANITIZER}=ON"
fi
@@ -53,9 +55,8 @@ prepare_build() {
cd "${BUILD_DIR}"
echo "Configuring with '${CMAKE_FLAGS} $@'."
cmake -G Ninja ${CMAKE_FLAGS} "$@" "${CI_BUILD_DIR}"
-}
-build_nvim() {
+
echo "Building nvim."
if ! top_make nvim ; then
exit 1
diff --git a/ci/common/submit_coverage.sh b/ci/common/submit_coverage.sh
index cb6ab62b5b..f781ca8e5e 100755
--- a/ci/common/submit_coverage.sh
+++ b/ci/common/submit_coverage.sh
@@ -4,7 +4,7 @@
# Args:
# $1: Flag(s) for codecov, separated by comma.
-set -ex
+set -e
# Change to grandparent dir (POSIXly).
CDPATH='' cd -P -- "$(dirname -- "$0")/../.." || exit
diff --git a/ci/common/suite.sh b/ci/common/suite.sh
index f6a8c22d21..5110e22ec2 100644
--- a/ci/common/suite.sh
+++ b/ci/common/suite.sh
@@ -11,49 +11,20 @@ FAIL_SUMMARY=""
END_MARKER="$BUILD_DIR/.tests_finished"
FAIL_SUMMARY_FILE="$BUILD_DIR/.test_errors"
-ci_fold() {
- if test "$GITHUB_ACTIONS" = "true"; then
- local action="$1"
- local name="$2"
- case "$action" in
- start)
- echo "::group::${name}"
- ;;
- end)
- echo "::endgroup::"
- ;;
- *)
- :;;
- esac
- fi
-}
-
enter_suite() {
- set +x
FAILED=0
rm -f "${END_MARKER}"
local suite_name="$1"
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE}/$suite_name"
- ci_fold "start" "$suite_name"
- set -x
}
exit_suite() {
- set +x
if test $FAILED -ne 0 ; then
echo "Suite ${NVIM_TEST_CURRENT_SUITE} failed, summary:"
echo "${FAIL_SUMMARY}"
- else
- ci_fold "end" ""
fi
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE%/*}"
- if test "$1" != "--continue" ; then
- exit $FAILED
- else
- local saved_failed=$FAILED
- FAILED=0
- return $saved_failed
- fi
+ FAILED=0
}
fail() {
@@ -71,16 +42,15 @@ fail() {
FAILED=1
}
-run_test() {
- local cmd="$1"
- local test_name="$2"
- eval "$cmd" || fail "$test_name"
-}
-
ended_successfully() {
if test -f "${FAIL_SUMMARY_FILE}" ; then
echo 'Test failed, complete summary:'
cat "${FAIL_SUMMARY_FILE}"
+
+ if [[ "$GITHUB_ACTIONS" == "true" ]]; then
+ rm -f "$FAIL_SUMMARY_FILE"
+ fi
+
return 1
fi
if ! test -f "${END_MARKER}" ; then
@@ -100,7 +70,7 @@ run_suite() {
local suite_name="$2"
enter_suite "$suite_name"
- run_test "$command" "$suite_name"
- exit_suite --continue
+ eval "$command" || fail "$suite_name"
+ exit_suite
}
diff --git a/ci/common/test.sh b/ci/common/test.sh
index 798f2926c6..f211a2e7aa 100644
--- a/ci/common/test.sh
+++ b/ci/common/test.sh
@@ -87,18 +87,15 @@ check_sanitizer() {
}
run_unittests() {(
- enter_suite unittests
ulimit -c unlimited || true
if ! build_make unittest ; then
fail 'unittests' F 'Unit tests failed'
fi
submit_coverage unittest
check_core_dumps "$(command -v luajit)"
- exit_suite
)}
run_functionaltests() {(
- enter_suite functionaltests
ulimit -c unlimited || true
if ! build_make ${FUNCTIONALTEST}; then
fail 'functionaltests' F 'Functional tests failed'
@@ -107,11 +104,9 @@ run_functionaltests() {(
check_sanitizer "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
- exit_suite
)}
run_oldtests() {(
- enter_suite oldtests
ulimit -c unlimited || true
if ! make oldtest; then
reset
@@ -121,11 +116,9 @@ run_oldtests() {(
check_sanitizer "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
- exit_suite
)}
check_runtime_files() {(
- set +x
local test_name="$1" ; shift
local message="$1" ; shift
local tst="$1" ; shift
@@ -146,7 +139,6 @@ check_runtime_files() {(
)}
install_nvim() {(
- enter_suite 'install_nvim'
if ! build_make install ; then
fail 'install' E 'make install failed'
exit_suite
@@ -179,6 +171,4 @@ install_nvim() {(
if ! grep -q "$gpat" "${INSTALL_PREFIX}/share/nvim/runtime/$genvimsynf" ; then
fail 'funcnames' F "It appears that $genvimsynf does not contain $gpat."
fi
-
- exit_suite
)}
diff --git a/ci/run_lint.sh b/ci/run_lint.sh
index 607ffa233a..2fea7a40c0 100755
--- a/ci/run_lint.sh
+++ b/ci/run_lint.sh
@@ -8,10 +8,34 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CI_DIR}/common/build.sh"
source "${CI_DIR}/common/suite.sh"
-run_suite 'make clint-full' 'clint'
-run_suite 'make lualint' 'lualint'
-run_suite 'make pylint' 'pylint'
-run_suite 'make shlint' 'shlint'
-run_suite 'make check-single-includes' 'single-includes'
+if [[ "$GITHUB_ACTIONS" != "true" ]]; then
+ run_suite 'make clint-full' 'clint'
+ run_suite 'make lualint' 'lualint'
+ run_suite 'make pylint' 'pylint'
+ run_suite 'make shlint' 'shlint'
+ run_suite 'make check-single-includes' 'single-includes'
-end_tests
+ end_tests
+else
+ case "$1" in
+ clint)
+ run_suite 'make clint-full' 'clint'
+ ;;
+ lualint)
+ run_suite 'make lualint' 'lualint'
+ ;;
+ pylint)
+ run_suite 'make pylint' 'pylint'
+ ;;
+ shlint)
+ run_suite 'make shlint' 'shlint'
+ ;;
+ single-includes)
+ run_suite 'make check-single-includes' 'single-includes'
+ ;;
+ *)
+ :;;
+ esac
+
+ end_tests
+fi
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
index 6c310ccc76..ae85246ab6 100755
--- a/ci/run_tests.sh
+++ b/ci/run_tests.sh
@@ -8,29 +8,42 @@ source "${CI_DIR}/common/build.sh"
source "${CI_DIR}/common/test.sh"
source "${CI_DIR}/common/suite.sh"
-enter_suite build
-check_core_dumps --delete quiet
+if [[ "$GITHUB_ACTIONS" != "true" ]]; then
+ run_suite 'build_nvim' 'build'
-prepare_build
-build_nvim
-
-exit_suite --continue
-
-enter_suite tests
-
-if test "$CLANG_SANITIZER" != "TSAN" ; then
- # Additional threads are only created when the builtin UI starts, which
- # doesn't happen in the unit/functional tests
- if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then
- run_test run_unittests unittests
+ if test "$CLANG_SANITIZER" != "TSAN"; then
+ # Additional threads are only created when the builtin UI starts, which
+ # doesn't happen in the unit/functional tests
+ if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then
+ run_suite run_unittests unittests
+ fi
+ run_suite run_functionaltests functionaltests
fi
- run_test run_functionaltests functionaltests
+ run_suite run_oldtests oldtests
+ run_suite install_nvim install_nvim
+
+ end_tests
+else
+ case "$1" in
+ build)
+ run_suite 'build_nvim' 'build'
+ ;;
+ unittests)
+ run_suite 'run_unittests' 'unittests'
+ ;;
+ functionaltests)
+ run_suite 'run_functionaltests' 'functionaltests'
+ ;;
+ oldtests)
+ run_suite 'run_oldtests' 'oldtests'
+ ;;
+ install_nvim)
+ run_suite 'install_nvim' 'install_nvim'
+ ;;
+ *)
+ :;;
+ esac
+
+ end_tests
fi
-run_test run_oldtests oldtests
-
-run_test install_nvim install_nvim
-
-exit_suite --continue
-
-end_tests
diff --git a/runtime/autoload/freebasic.vim b/runtime/autoload/freebasic.vim
index fe6d2745be..428cf1382b 100644
--- a/runtime/autoload/freebasic.vim
+++ b/runtime/autoload/freebasic.vim
@@ -23,8 +23,7 @@ function! freebasic#GetDialect() abort
let save_cursor = getcurpos()
call cursor(1, 1)
- " let lnum = search(pat, 'n', '', '', skip) " 'skip' needs 8.2.0915
- let lnum = search(pat, 'n', '', '')
+ let lnum = search(pat, 'n', '', '', skip)
call setpos('.', save_cursor)
if lnum
diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim
index 1d462ad02c..ec030adf04 100644
--- a/runtime/autoload/health.vim
+++ b/runtime/autoload/health.vim
@@ -21,10 +21,17 @@ function! health#check(plugin_names) abort
throw 'healthcheck_not_found'
endif
eval type == 'v' ? call(func, []) : luaeval(func)
+ " in the event the healthcheck doesn't return anything
+ " (the plugin author should avoid this possibility)
+ if len(s:output) == 0
+ throw 'healthcheck_no_return_value'
+ endif
catch
let s:output = [] " Clear the output
if v:exception =~# 'healthcheck_not_found'
call health#report_error('No healthcheck found for "'.name.'" plugin.')
+ elseif v:exception =~# 'healthcheck_no_return_value'
+ call health#report_error('The healthcheck report for "'.name.'" plugin is empty.')
else
call health#report_error(printf(
\ "Failed to run healthcheck for \"%s\" plugin. Exception:\n%s\n%s",
@@ -127,7 +134,7 @@ endfunction " }}}
" From a path return a list [{name}, {func}, {type}] representing a healthcheck
function! s:filepath_to_healthcheck(path) abort
- if a:path =~# 'vim$'
+ if a:path =~# 'vim$'
let name = matchstr(a:path, '\zs[^\/]*\ze\.vim$')
let func = 'health#'.name.'#check'
let type = 'v'
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 2da1f5e40d..de7db3b0b7 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -186,7 +186,7 @@ About the `functions` map:
a type name, e.g. `nvim_buf_get_lines` is the `get_lines` method of
a Buffer instance. |dev-api|
- Global functions have the "method=false" flag and are prefixed with just
- `nvim_`, e.g. `nvim_get_buffers`.
+ `nvim_`, e.g. `nvim_list_bufs`.
*api-mapping*
External programs (clients) can use the metadata to discover the API, using
@@ -855,7 +855,7 @@ nvim_exec_lua({code}, {args}) *nvim_exec_lua()*
Return: ~
Return value of Lua code if present or NIL.
-nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()*
+nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()*
Sends input-keys to Nvim, subject to various quirks controlled
by `mode` flags. This is a blocking call, unlike
|nvim_input()|.
@@ -863,23 +863,25 @@ nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()*
On execution error: does not fail, but updates v:errmsg.
To input sequences like <C-o> use |nvim_replace_termcodes()|
- (typically with escape_csi=true) to replace |keycodes|, then
+ (typically with escape_ks=false) to replace |keycodes|, then
pass the result to nvim_feedkeys().
Example: >
:let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
- :call nvim_feedkeys(key, 'n', v:true)
+ :call nvim_feedkeys(key, 'n', v:false)
<
Parameters: ~
- {keys} to be typed
- {mode} behavior flags, see |feedkeys()|
- {escape_csi} If true, escape K_SPECIAL/CSI bytes in
- `keys`
+ {keys} to be typed
+ {mode} behavior flags, see |feedkeys()|
+ {escape_ks} If true, escape K_SPECIAL bytes in `keys`
+ This should be false if you already used
+ |nvim_replace_termcodes()|, and true
+ otherwise.
See also: ~
feedkeys()
- vim_strsave_escape_csi
+ vim_strsave_escape_ks
nvim_get_all_options_info() *nvim_get_all_options_info()*
Gets the option information for all options.
@@ -1538,14 +1540,13 @@ nvim_set_current_win({window}) *nvim_set_current_win()*
Parameters: ~
{window} Window handle
-nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
+nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()*
Set a highlight group.
- TODO: ns_id = 0, should modify :highlight namespace TODO val
- should take update vs reset flag
-
Parameters: ~
- {ns_id} number of namespace for this highlight
+ {ns_id} number of namespace for this highlight. Use value
+ 0 to set a highlight group in the global (
+ `:highlight` ) namespace.
{name} highlight group name, like ErrorMsg
{val} highlight definition map, like
|nvim_get_hl_by_name|. in addition the following
@@ -1583,7 +1584,7 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()*
{rhs} Right-hand-side |{rhs}| of the mapping.
{opts} Optional parameters map. Accepts all
|:map-arguments| as keys excluding |<buffer>| but
- including |noremap| and "desc". |desc| can be used
+ including |noremap| and "desc". "desc" can be used
to give a description to keymap. When called from
Lua, also accepts a "callback" key that takes a
Lua function to call when the mapping is executed.
@@ -2151,6 +2152,29 @@ nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
Return: ~
Option value
+ *nvim_buf_get_text()*
+nvim_buf_get_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
+ {opts})
+ Gets a range from the buffer.
+
+ This differs from |nvim_buf_get_lines()| in that it allows
+ retrieving only portions of a line.
+
+ Indexing is zero-based. Column indices are end-exclusive.
+
+ Prefer |nvim_buf_get_lines()| when retrieving entire lines.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {start_row} First line index
+ {start_col} Starting byte offset of first line
+ {end_row} Last line index
+ {end_col} Ending byte offset of last line (exclusive)
+ {opts} Optional parameters. Currently unused.
+
+ Return: ~
+ Array of lines, or empty array for unloaded buffer.
+
nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()*
Gets a buffer-scoped (b:) variable.
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 5e50f9c1f8..dbe70b84cf 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -525,8 +525,19 @@ DirChanged After the |current-directory| was changed.
"global" to trigger on `:cd`
"auto" to trigger on 'autochdir'.
Sets these |v:event| keys:
- cwd: current working directory
- scope: "global", "tab", "window"
+ cwd: current working directory
+ scope: "global", "tabpage", "window"
+ changed_window: v:true if we fired the event
+ switching window (or tab)
+ <afile> is set to the new directory name.
+ Non-recursive (event cannot trigger itself).
+ *DirChangedPre*
+DirChangedPre When the |current-directory| is going to be
+ changed, as with |DirChanged|.
+ The pattern is like with |DirChanged|.
+ Sets these |v:event| keys:
+ directory: new working directory
+ scope: "global", "tabpage", "window"
changed_window: v:true if we fired the event
switching window (or tab)
<afile> is set to the new directory name.
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 56bc8bfb3e..833da2622c 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -22,8 +22,10 @@ acos({expr}) Float arc cosine of {expr}
add({object}, {item}) List/Blob append {item} to {object}
and({expr}, {expr}) Number bitwise AND
api_info() Dict api metadata
-append({lnum}, {string}) Number append {string} below line {lnum}
-append({lnum}, {list}) Number append lines {list} below line {lnum}
+append({lnum}, {text}) Number append {text} below line {lnum}
+appendbufline({expr}, {lnum}, {text})
+ Number append {text} below line {lnum}
+ in buffer {expr}
argc([{winid}]) Number number of files in the argument list
argidx() Number current index in the argument list
arglistid([{winnr} [, {tabnr}]]) Number argument list id
@@ -52,7 +54,7 @@ assert_notmatch({pat}, {text} [, {msg}])
assert_report({msg}) Number report a test failure
assert_true({actual} [, {msg}]) Number assert {actual} is true
atan({expr}) Float arc tangent of {expr}
-atan2({expr}, {expr}) Float arc tangent of {expr1} / {expr2}
+atan2({expr1}, {expr2}) Float arc tangent of {expr1} / {expr2}
browse({save}, {title}, {initdir}, {default})
String put up a file requester
browsedir({title}, {initdir}) String put up a directory requester
@@ -72,9 +74,9 @@ call({func}, {arglist} [, {dict}])
any call {func} with arguments {arglist}
ceil({expr}) Float round {expr} up
changenr() Number current change number
-chanclose({id}[, {stream}]) Number Closes a channel or one of its streams
+chanclose({id} [, {stream}]) Number Closes a channel or one of its streams
chansend({id}, {data}) Number Writes {data} to channel
-char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
+char2nr({expr} [, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
charcol({expr}) Number column number of cursor or mark
charidx({string}, {idx} [, {countcc}])
Number char index of byte {idx} in {string}
@@ -91,8 +93,8 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
copy({expr}) any make a shallow copy of {expr}
cos({expr}) Float cosine of {expr}
cosh({expr}) Float hyperbolic cosine of {expr}
-count({list}, {expr} [, {ic} [, {start}]])
- Number count how many {expr} are in {list}
+count({comp}, {expr} [, {ic} [, {start}]])
+ Number count how many {expr} are in {comp}
cscope_connection([{num}, {dbpath} [, {prepend}]])
Number checks existence of cscope connection
ctxget([{index}]) Dict return the |context| dict at {index}
@@ -100,7 +102,7 @@ ctxpop() none pop and restore |context| from the
|context-stack|
ctxpush([{types}]) none push the current |context| to the
|context-stack|
-ctxset({context}[, {index}]) none set |context| at {index}
+ctxset({context} [, {index}]) none set |context| at {index}
ctxsize() Number return |context-stack| size
cursor({lnum}, {col} [, {off}])
Number move cursor to {lnum}, {col}, {off}
@@ -108,7 +110,7 @@ cursor({list}) Number move cursor to position in {list}
debugbreak({pid}) Number interrupt process being debugged
deepcopy({expr} [, {noref}]) any make a full copy of {expr}
delete({fname} [, {flags}]) Number delete the file or directory {fname}
-deletebufline({buf}, {first}[, {last}])
+deletebufline({buf}, {first} [, {last}])
Number delete lines from buffer {buf}
dictwatcheradd({dict}, {pattern}, {callback})
Start watching a dictionary
@@ -212,7 +214,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}
-getwininfo([{winid}]) List list of windows
+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
getwinposy() Number Y coord in pixels of Vim window
@@ -262,9 +264,9 @@ items({dict}) List key-value pairs in {dict}
jobpid({id}) Number Returns pid of a job.
jobresize({id}, {width}, {height})
Number Resize pseudo terminal window of a job
-jobstart({cmd}[, {opts}]) Number Spawns {cmd} as a job
+jobstart({cmd} [, {opts}]) Number Spawns {cmd} as a job
jobstop({id}) Number Stops a job
-jobwait({ids}[, {timeout}]) Number Wait for a set of jobs
+jobwait({ids} [, {timeout}]) Number Wait for a set of jobs
join({list} [, {sep}]) String join {list} items into one String
json_decode({expr}) any Convert {expr} from JSON
json_encode({expr}) String Convert {expr} to JSON
@@ -279,28 +281,32 @@ list2str({list} [, {utf8}]) String turn numbers in {list} into a String
localtime() Number current time
log({expr}) Float natural logarithm (base e) of {expr}
log10({expr}) Float logarithm of Float {expr} to base 10
-luaeval({expr}[, {expr}]) any evaluate Lua expression
+luaeval({expr} [, {expr}]) any evaluate |Lua| expression
map({expr1}, {expr2}) List/Dict change each item in {expr1} to {expr}
-maparg({name}[, {mode} [, {abbr} [, {dict}]]])
+maparg({name} [, {mode} [, {abbr} [, {dict}]]])
String or Dict
rhs of mapping {name} in mode {mode}
-mapcheck({name}[, {mode} [, {abbr}]])
+mapcheck({name} [, {mode} [, {abbr}]])
String check for mappings matching {name}
-match({expr}, {pat}[, {start}[, {count}]])
+match({expr}, {pat} [, {start} [, {count}]])
Number position where {pat} matches in {expr}
-matchadd({group}, {pattern}[, {priority}[, {id}]])
+matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
Number highlight {pattern} with {group}
-matchaddpos({group}, {list}[, {priority}[, {id}]])
+matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])
Number highlight positions with {group}
matcharg({nr}) List arguments of |:match|
matchdelete({id} [, {win}]) Number delete match identified by {id}
-matchend({expr}, {pat}[, {start}[, {count}]])
+matchend({expr}, {pat} [, {start} [, {count}]])
Number position where {pat} ends in {expr}
-matchlist({expr}, {pat}[, {start}[, {count}]])
+matchfuzzy({list}, {str} [, {dict}])
+ List fuzzy match {str} in {list}
+matchfuzzypos({list}, {str} [, {dict}])
+ List fuzzy match {str} in {list}
+matchlist({expr}, {pat} [, {start} [, {count}]])
List match and submatches of {pat} in {expr}
-matchstr({expr}, {pat}[, {start}[, {count}]])
+matchstr({expr}, {pat} [, {start} [, {count}]])
String {count}'th match of {pat} in {expr}
-matchstrpos({expr}, {pat}[, {start}[, {count}]])
+matchstrpos({expr}, {pat} [, {start} [, {count}]])
List {count}'th match of {pat} in {expr}
max({expr}) Number maximum value of items in {expr}
menu_get({path} [, {modes}]) List description of |menus| matched by {path}
@@ -311,7 +317,7 @@ mode([expr]) String current editing mode
msgpackdump({list} [, {type}]) List/Blob dump objects to msgpack
msgpackparse({data}) List parse msgpack to a list of objects
nextnonblank({lnum}) Number line nr of non-blank line >= {lnum}
-nr2char({expr}[, {utf8}]) String single char with ASCII/UTF-8 value {expr}
+nr2char({expr} [, {utf8}]) String single char with ASCII/UTF-8 value {expr}
nvim_...({args}...) any call nvim |api| functions
or({expr}, {expr}) Number bitwise OR
pathshorten({expr} [, {len}]) String shorten directory names in a path
@@ -334,6 +340,8 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
+reduce({object}, {func} [, {initial}])
+ any reduce {object} using {func}
reg_executing() String get the executing register name
reg_recorded() String get the last recorded register name
reg_recording() String get the recording register name
@@ -361,9 +369,9 @@ resolve({filename}) String get filename a shortcut points to
reverse({list}) List reverse {list} in-place
round({expr}) Float round off {expr}
rubyeval({expr}) any evaluate |Ruby| expression
-rpcnotify({channel}, {event}[, {args}...])
+rpcnotify({channel}, {event} [, {args}...])
Sends an |RPC| notification to {channel}
-rpcrequest({channel}, {method}[, {args}...])
+rpcrequest({channel}, {method} [, {args}...])
Sends an |RPC| request to {channel}
screenattr({row}, {col}) Number attribute at screen position
screenchar({row}, {col}) Number character at screen position
@@ -372,7 +380,7 @@ screencol() Number current cursor column
screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character
screenrow() Number current cursor row
screenstring({row}, {col}) String characters at screen position
-search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
+search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Number search for {pattern}
searchcount([{options}]) Dict Get or update the last search count
searchdecl({name} [, {global} [, {thisblock}]])
@@ -381,13 +389,13 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
Number search for other end of start/end pair
searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
List search for other end of start/end pair
-searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]])
+searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
List search for {pattern}
server2client({clientid}, {string})
Number send reply string
serverlist() String get a list of available servers
-setbufline( {expr}, {lnum}, {line})
- Number set line {lnum} to {line} in buffer
+setbufline({expr}, {lnum}, {text})
+ Number set line {lnum} to {text} in buffer
{expr}
setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val}
setcharpos({expr}, {list}) Number set the {expr} position to {list}
@@ -406,7 +414,7 @@ setpos({expr}, {list}) Number set the {expr} position to {list}
setqflist({list} [, {action}]) Number modify quickfix list using {list}
setqflist({list}, {action}, {what})
Number modify specific quickfix list props
-setreg({n}, {v}[, {opt}]) Number set register to value and type
+setreg({n}, {v} [, {opt}]) Number set register to value and type
settabvar({nr}, {varname}, {val}) set {varname} in tab page {nr} to {val}
settabwinvar({tabnr}, {winnr}, {varname}, {val}) set {varname} in window
{winnr} in tab page {tabnr} to {val}
@@ -491,9 +499,9 @@ system({cmd} [, {input}]) String output of shell command/filter {cmd}
systemlist({cmd} [, {input}]) List output of shell command/filter {cmd}
tabpagebuflist([{arg}]) List list of buffer numbers in tab page
tabpagenr([{arg}]) Number number of current or last tab page
-tabpagewinnr({tabarg}[, {arg}])
+tabpagewinnr({tabarg} [, {arg}])
Number number of current window in tab page
-taglist({expr}[, {filename}]) List list of tags matching {expr}
+taglist({expr} [, {filename}]) List list of tags matching {expr}
tagfiles() List tags files used
tan({expr}) Float tangent of {expr}
tanh({expr}) Float hyperbolic tangent of {expr}
@@ -520,7 +528,7 @@ uniq({list} [, {func} [, {dict}]])
values({dict}) List values in {dict}
virtcol({expr}) Number screen column of cursor or mark
visualmode([expr]) String last visual mode used
-wait({timeout}, {condition}[, {interval}])
+wait({timeout}, {condition} [, {interval}])
Number Wait until {condition} is satisfied
wildmenumode() Number whether 'wildmenu' mode is active
win_execute({id}, {command} [, {silent}])
@@ -995,7 +1003,7 @@ changenr() *changenr()*
redo it is the number of the redone change. After undo it is
one less than the number of the undone change.
-chanclose({id}[, {stream}]) *chanclose()*
+chanclose({id} [, {stream}]) *chanclose()*
Close a channel or a specific stream associated with it.
For a job, {stream} can be one of "stdin", "stdout",
"stderr" or "rpc" (closes stdin/stdout for a job started
@@ -1439,7 +1447,7 @@ ctxpush([{types}]) *ctxpush()*
which |context-types| to include in the pushed context.
Otherwise, all context types are included.
-ctxset({context}[, {index}]) *ctxset()*
+ctxset({context} [, {index}]) *ctxset()*
Sets the |context| at {index} from the top of the
|context-stack| to that represented by {context}.
{context} is a Dictionary with context data (|context-dict|).
@@ -1483,7 +1491,7 @@ cursor({list})
Can also be used as a |method|: >
GetCursorPos()->cursor()
-deepcopy({expr}[, {noref}]) *deepcopy()* *E698*
+deepcopy({expr} [, {noref}]) *deepcopy()* *E698*
Make a copy of {expr}. For Numbers and Strings this isn't
different from using {expr} directly.
When {expr} is a |List| a full copy is created. This means
@@ -1526,7 +1534,7 @@ delete({fname} [, {flags}]) *delete()*
Can also be used as a |method|: >
GetName()->delete()
-deletebufline({buf}, {first}[, {last}]) *deletebufline()*
+deletebufline({buf}, {first} [, {last}]) *deletebufline()*
Delete lines {first} to {last} (inclusive) from buffer {buf}.
If {last} is omitted then delete line {first} only.
On success 0 is returned, on failure 1 is returned.
@@ -1536,7 +1544,7 @@ deletebufline({buf}, {first}[, {last}]) *deletebufline()*
For the use of {buf}, see |bufname()| above.
- {first} and {last} are used like with |setline()|. Note that
+ {first} and {last} are used like with |getline()|. Note that
when using |line()| this refers to the current buffer. Use "$"
to refer to the last line in buffer {buf}.
@@ -1799,6 +1807,7 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
exists("$HOSTNAME")
exists("*strftime")
exists("*s:MyFunc")
+ exists("*MyFunc")
exists("bufcount")
exists(":Make")
exists("#CursorHold")
@@ -2861,7 +2870,7 @@ getcursorcharpos([{winid}])
Can also be used as a |method|: >
GetWinid()->getcursorcharpos()
-getcwd([{winnr}[, {tabnr}]]) *getcwd()*
+getcwd([{winnr} [, {tabnr}]]) *getcwd()*
With no arguments, returns the name of the effective
|current-directory|. With {winnr} or {tabnr} the working
directory of that scope is returned, and 'autochdir' is
@@ -3018,7 +3027,7 @@ getline({lnum} [, {end}])
< To get lines from another buffer see |getbufline()|
-getloclist({nr},[, {what}]) *getloclist()*
+getloclist({nr} [, {what}]) *getloclist()*
Returns a |List| with all the entries in the location list for
window {nr}. {nr} can be the window number or the |window-ID|.
When {nr} is zero the current window is used.
@@ -3645,7 +3654,7 @@ has_key({dict}, {key}) *has_key()*
Can also be used as a |method|: >
mydict->has_key(key)
-haslocaldir([{winnr}[, {tabnr}]]) *haslocaldir()*
+haslocaldir([{winnr} [, {tabnr}]]) *haslocaldir()*
The result is a Number, which is 1 when the window has set a
local path via |:lcd| or when {winnr} is -1 and the tabpage
has set a local path via |:tcd|, otherwise 0.
@@ -4147,7 +4156,7 @@ jobresize({job}, {width}, {height}) *jobresize()*
columns and {height} rows.
Fails if the job was not started with `"pty":v:true`.
-jobstart({cmd}[, {opts}]) *jobstart()*
+jobstart({cmd} [, {opts}]) *jobstart()*
Spawns {cmd} as a job.
If {cmd} is a List it runs directly (no 'shell').
If {cmd} is a String it runs in the 'shell', like this: >
@@ -4234,7 +4243,7 @@ jobstop({id}) *jobstop()*
Returns 1 for valid job id, 0 for invalid id, including jobs have
exited or stopped.
-jobwait({jobs}[, {timeout}]) *jobwait()*
+jobwait({jobs} [, {timeout}]) *jobwait()*
Waits for jobs and their |on_exit| handlers to complete.
{jobs} is a List of |job-id|s to wait for.
@@ -4491,7 +4500,7 @@ log10({expr}) *log10()*
Can also be used as a |method|: >
Compute()->log10()
-luaeval({expr}[, {expr}])
+luaeval({expr} [, {expr}])
Evaluate Lua expression {expr} and return its result converted
to Vim data structures. See |lua-eval| for more details.
@@ -4711,7 +4720,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()*
GetList()->match('word')
<
*matchadd()* *E798* *E799* *E801* *E957*
-matchadd({group}, {pattern}[, {priority}[, {id} [, {dict}]]])
+matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
Defines a pattern to be highlighted in the current window (a
"match"). It will be highlighted with {group}. Returns an
identification number (ID), which can be used to delete the
@@ -4857,6 +4866,87 @@ matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()*
Can also be used as a |method|: >
GetText()->matchend('word')
+matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
+ If {list} is a list of strings, then returns a |List| with all
+ the strings in {list} that fuzzy match {str}. The strings in
+ the returned list are sorted based on the matching score.
+
+ The optional {dict} argument always supports the following
+ items:
+ matchseq When this item is present and {str} contains
+ multiple words separated by white space, then
+ returns only matches that contain the words in
+ the given sequence.
+
+ If {list} is a list of dictionaries, then the optional {dict}
+ argument supports the following additional items:
+ key key of the item which is fuzzy matched against
+ {str}. The value of this item should be a
+ string.
+ text_cb |Funcref| that will be called for every item
+ in {list} to get the text for fuzzy matching.
+ This should accept a dictionary item as the
+ argument and return the text for that item to
+ use for fuzzy matching.
+
+ {str} is treated as a literal string and regular expression
+ matching is NOT supported. The maximum supported {str} length
+ is 256.
+
+ When {str} has multiple words each separated by white space,
+ then the list of strings that have all the words is returned.
+
+ If there are no matching strings or there is an error, then an
+ empty list is returned. If length of {str} is greater than
+ 256, then returns an empty list.
+
+ Refer to |fuzzy-match| for more information about fuzzy
+ matching strings.
+
+ Example: >
+ :echo matchfuzzy(["clay", "crow"], "cay")
+< results in ["clay"]. >
+ :echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
+< results in a list of buffer names fuzzy matching "ndl". >
+ :echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'})
+< results in a list of buffer information dicts with buffer
+ names fuzzy matching "ndl". >
+ :echo getbufinfo()->matchfuzzy("spl",
+ \ {'text_cb' : {v -> v.name}})
+< results in a list of buffer information dicts with buffer
+ names fuzzy matching "spl". >
+ :echo v:oldfiles->matchfuzzy("test")
+< results in a list of file names fuzzy matching "test". >
+ :let l = readfile("buffer.c")->matchfuzzy("str")
+< results in a list of lines in "buffer.c" fuzzy matching "str". >
+ :echo ['one two', 'two one']->matchfuzzy('two one')
+< results in ['two one', 'one two']. >
+ :echo ['one two', 'two one']->matchfuzzy('two one',
+ \ {'matchseq': 1})
+< results in ['two one'].
+
+matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()*
+ Same as |matchfuzzy()|, but returns the list of matched
+ strings, the list of character positions where characters
+ in {str} matches and a list of matching scores. You can
+ use |byteidx()| to convert a character position to a byte
+ position.
+
+ If {str} matches multiple times in a string, then only the
+ positions for the best match is returned.
+
+ If there are no matching strings or there is an error, then a
+ list with three empty list items is returned.
+
+ Example: >
+ :echo matchfuzzypos(['testing'], 'tsg')
+< results in [['testing'], [[0, 2, 6]], [99]] >
+ :echo matchfuzzypos(['clay', 'lacy'], 'la')
+< results in [['lacy', 'clay'], [[0, 1], [1, 2]], [153, 133]] >
+ :echo [{'text': 'hello', 'id' : 10}]
+ \ ->matchfuzzypos('ll', {'key' : 'text'})
+< results in [[{'id': 10, 'text': 'hello'}], [[2, 3]], [127]]
+
matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()*
Same as |match()|, but return a |List|. The first item in the
list is the matched string, same as what matchstr() would
@@ -5685,6 +5775,25 @@ readfile({fname} [, {type} [, {max}]])
Can also be used as a |method|: >
GetFileName()->readfile()
+reduce({object}, {func} [, {initial}]) *reduce()* *E998*
+ {func} is called for every item in {object}, which can be a
+ |List| or a |Blob|. {func} is called with two arguments: the
+ result so far and current item. After processing all items
+ the result is returned.
+
+ {initial} is the initial result. When omitted, the first item
+ in {object} is used and {func} is first called for the second
+ item. If {initial} is not given and {object} is empty no
+ result can be computed, an E998 error is given.
+
+ Examples: >
+ echo reduce([1, 3, 5], { acc, val -> acc + val })
+ echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+ echo reduce(0z1122, { acc, val -> 2 * acc + val })
+<
+ Can also be used as a |method|: >
+ echo mylist->reduce({ acc, val -> acc + val }, 0)
+
reg_executing() *reg_executing()*
Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed.
@@ -5755,16 +5864,22 @@ reltimestr({time}) *reltimestr()*
<
*remote_expr()* *E449*
remote_expr({server}, {string} [, {idvar} [, {timeout}]])
- Send the {string} to {server}. The string is sent as an
- expression and the result is returned after evaluation.
- The result must be a String or a |List|. A |List| is turned
- into a String by joining the items with a line break in
- between (not at the end), like with join(expr, "\n").
+ Send the {string} to {server}. The {server} argument is a
+ string, also see |{server}|.
+
+ The string is sent as an expression and the result is returned
+ after evaluation. The result must be a String or a |List|. A
+ |List| is turned into a String by joining the items with a
+ line break in between (not at the end), like with join(expr,
+ "\n").
+
If {idvar} is present and not empty, it is taken as the name
of a variable and a {serverid} for later use with
|remote_read()| is stored there.
+
If {timeout} is given the read times out after this many
seconds. Otherwise a timeout of 600 seconds is used.
+
See also |clientserver| |RemoteReply|.
This function is not available in the |sandbox|.
Note: Any errors will cause a local error message to be issued
@@ -5782,7 +5897,7 @@ remote_expr({server}, {string} [, {idvar} [, {timeout}]])
remote_foreground({server}) *remote_foreground()*
Move the Vim server with the name {server} to the foreground.
- The {server} argument is a string.
+ The {server} argument is a string, also see |{server}|.
This works like: >
remote_expr({server}, "foreground()")
< Except that on Win32 systems the client does the work, to work
@@ -5818,12 +5933,17 @@ remote_read({serverid}, [{timeout}]) *remote_read()*
<
*remote_send()* *E241*
remote_send({server}, {string} [, {idvar}])
- Send the {string} to {server}. The string is sent as input
- keys and the function returns immediately. At the Vim server
- the keys are not mapped |:map|.
+ Send the {string} to {server}. The {server} argument is a
+ string, also see |{server}|.
+
+ The string is sent as input keys and the function returns
+ immediately. At the Vim server the keys are not mapped
+ |:map|.
+
If {idvar} is present, it is taken as the name of a variable
and a {serverid} for later use with remote_read() is stored
there.
+
See also |clientserver| |RemoteReply|.
This function is not available in the |sandbox|.
@@ -5943,19 +6063,19 @@ round({expr}) *round()*
Can also be used as a |method|: >
Compute()->round()
-rpcnotify({channel}, {event}[, {args}...]) *rpcnotify()*
+rpcnotify({channel}, {event} [, {args}...]) *rpcnotify()*
Sends {event} to {channel} via |RPC| and returns immediately.
If {channel} is 0, the event is broadcast to all channels.
Example: >
:au VimLeave call rpcnotify(0, "leaving")
-rpcrequest({channel}, {method}[, {args}...]) *rpcrequest()*
+rpcrequest({channel}, {method} [, {args}...]) *rpcrequest()*
Sends a request to {channel} to invoke {method} via
|RPC| and blocks until a response is received.
Example: >
:let result = rpcrequest(rpc_chan, "func", 1, 2, 3)
-rpcstart({prog}[, {argv}]) *rpcstart()*
+rpcstart({prog} [, {argv}]) *rpcstart()*
Deprecated. Replace >
:let id = rpcstart('prog', ['arg1', 'arg2'])
< with >
@@ -6061,8 +6181,9 @@ screenstring({row}, {col}) *screenstring()*
Can also be used as a |method|: >
GetRow()->screenstring(col)
-
-search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
+<
+ *search()*
+search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Search for regexp pattern {pattern}. The search starts at the
cursor position (you can use |cursor()| to set it).
@@ -6114,6 +6235,15 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
The value must not be negative. A zero value is like not
giving the argument.
+ If the {skip} expression is given it is evaluated with the
+ cursor positioned on the start of a match. If it evaluates to
+ non-zero this match is skipped. This can be used, for
+ example, to skip a match in a comment or a string.
+ {skip} can be a string, which is evaluated as an expression, a
+ function reference or a lambda.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
*search()-sub-match*
With the 'p' flag the returned value is one more than the
first sub-match in \(\). One if none of them matched but the
@@ -6397,7 +6527,8 @@ searchpairpos({start}, {middle}, {end} [, {flags} [, {skip}
<
See |match-parens| for a bigger and more useful example.
-searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()*
+ *searchpos()*
+searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Same as |search()|, but returns a |List| with the line and
column position of the match. The first element of the |List|
is the line number and the second element is the byte index of
@@ -7325,6 +7456,9 @@ stdioopen({opts}) *stdioopen()*
{opts} is a dictionary with these keys:
|on_stdin| : callback invoked when stdin is written to.
+ on_print : callback invoked when Nvim needs to print a
+ message, with the message (whose type is string)
+ as sole argument.
stdin_buffered : read stdin in |channel-buffered| mode.
rpc : If set, |msgpack-rpc| will be used to communicate
over stdio
@@ -7723,11 +7857,11 @@ substitute({string}, {pat}, {sub}, {flags}) *substitute()*
swapinfo({fname}) *swapinfo()*
The result is a dictionary, which holds information about the
swapfile {fname}. The available fields are:
- version VIM version
+ version Vim version
user user name
host host name
fname original file name
- pid PID of the VIM process that created the swap
+ pid PID of the Vim process that created the swap
file
mtime last modification time in seconds
inode Optional: INODE number of the file
@@ -8038,7 +8172,7 @@ tempname() *tempname()* *temp-file-name*
For MS-Windows forward slashes are used when the 'shellslash'
option is set or when 'shellcmdflag' starts with '-'.
-termopen({cmd}[, {opts}]) *termopen()*
+termopen({cmd} [, {opts}]) *termopen()*
Spawns {cmd} in a new pseudo-terminal session connected
to the current buffer. {cmd} is the same as the one passed to
|jobstart()|. This function fails if the current buffer is
@@ -8397,7 +8531,7 @@ visualmode([{expr}]) *visualmode()*
a non-empty String, then the Visual mode will be cleared and
the old value is returned. See |non-zero-arg|.
-wait({timeout}, {condition}[, {interval}]) *wait()*
+wait({timeout}, {condition} [, {interval}]) *wait()*
Waits until {condition} evaluates to |TRUE|, where {condition}
is a |Funcref| or |string| containing an expression.
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index e26a84f80f..b4d3304880 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1118,8 +1118,11 @@ register. With blockwise selection it also depends on the size of the block
and whether the corners are on an existing character. (Implementation detail:
it actually works by first putting the register after the selection and then
deleting the selection.)
-The previously selected text is put in the unnamed register. If you want to
-put the same text into a Visual selection several times you need to use
+With 'p' the previously selected text is put in the unnamed register. This is
+useful if you want to put that text somewhere else. But you cannot repeat the
+same change.
+With 'P' the unnamed register is not changed, you can repeat the same change.
+But the deleted text cannot be used. If you do need it you can use 'p' with
another register. E.g., yank the text to copy, Visually select the text to
replace and use "0p . You can repeat this as many times as you like, and the
unnamed register will be changed each time.
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index 3d0287b0cd..4ccf3f145c 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -196,7 +196,7 @@ If you want to keep the changed buffer without saving it, switch on the
Edit {file} always. Discard any changes to the
current buffer.
Also see |++opt| and |+cmd|.
-
+ *:edit_#* *:e#*
:e[dit] [++opt] [+cmd] #[count]
Edit the [count]th buffer (as shown by |:files|).
This command does the same as [count] CTRL-^. But ":e
@@ -356,7 +356,7 @@ as a wildcard when "[" is in the 'isfname' option. A simple way to avoid this
is to use "path\[[]abc]", this matches the file "path\[abc]".
*starstar-wildcard*
-Expanding "**" is possible on Unix, Win32, Mac OS/X and a few other systems.
+Expanding "**" is possible on Unix, Win32, macOS and a few other systems.
This allows searching a directory tree. This goes up to 100 directories deep.
Note there are some commands where this works slightly differently, see
|file-searching|.
@@ -1450,6 +1450,11 @@ If you don't get warned often enough you can use the following command.
if it exists now.
Once a file has been checked the timestamp is reset,
you will not be warned again.
+ Syntax highlighting, marks, diff status,
+ 'fileencoding', 'fileformat' and 'binary' options
+ are not changed. See |v:fcs_choice| to reload these
+ too (for example, if a code formatting tools has
+ changed the file).
:[N]checkt[ime] {filename}
:[N]checkt[ime] [N]
@@ -1490,7 +1495,7 @@ which version of the file you want to keep.
The accuracy of the time check depends on the filesystem. On Unix it is
usually sub-second. With old file sytems and on MS-Windows it is normally one
-second. Use has('nanotime') check if sub-second time stamp checks are
+second. Use `has('nanotime')` to check if sub-second time stamp checks are
available.
There is one situation where you get the message while there is nothing wrong:
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index fc788fba59..fc422f13e5 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1882,6 +1882,11 @@ v:fcs_choice What should happen after a |FileChangedShell| event was
do with the affected buffer:
reload Reload the buffer (does not work if
the file was deleted).
+ edit Reload the buffer and detect the
+ values for options such as
+ 'fileformat', 'fileencoding', 'binary'
+ (does not work if the file was
+ deleted).
ask Ask the user what to do, as if there
was no autocommand. Except that when
only the timestamp changed nothing
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index f02f9f8032..e3bc3d5437 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -923,7 +923,9 @@ tag command note action in Visual mode ~
before the highlighted area
|v_J| J 2 join the highlighted lines
|v_K| K run 'keywordprg' on the highlighted area
-|v_O| O move horizontally to other corner of area.
+|v_O| O move horizontally to other corner of area
+|v_P| P replace highlighted area with register
+ contents; unnamed register is unchanged
Q does not start Ex mode
|v_R| R 2 delete the highlighted lines and start
insert
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index f6fcbe8fb9..d717759444 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1225,8 +1225,8 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config})
},
-- Use a function to dynamically turn signs off
-- and on, using buffer local variables
- signs = function(bufnr, client_id)
- return vim.bo[bufnr].show_signs == false
+ signs = function(namespace, bufnr)
+ return vim.b[bufnr].show_signs == true
end,
-- Disable a feature
update_in_insert = false,
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 77f1dad6c7..355c31090e 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -595,13 +595,33 @@ vim.highlight.on_yank({opts}) *vim.highlight.on_yank()*
- {on_visual} highlight when yanking visual selection (default `true`)
- {event} event structure (default |v:event|)
-vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})
+vim.highlight.range({bufnr}, {ns}, {hlgroup}, {start}, {finish}, {opts})
*vim.highlight.range()*
- Highlights the range between {start} and {finish} (tuples of {line,col})
- in buffer {bufnr} with the highlight group {higroup} using the namespace
- {ns}. Optional arguments are the type of range (characterwise, linewise,
- or blockwise, see |setreg|; default to characterwise) and whether the
- range is inclusive (default false).
+
+ Apply highlight group to range of text.
+
+ Parameters: ~
+ {bufnr} buffer number
+ {ns} namespace for highlights
+ {hlgroup} highlight group name
+ {start} starting position (tuple {line,col})
+ {finish} finish position (tuple {line,col})
+ {opts} optional parameters:
+ • `regtype`: type of range (characterwise, linewise,
+ or blockwise, see |setreg|), default `'v'`
+ • `inclusive`: range includes end position, default
+ `false`
+ • `priority`: priority of highlight, default
+ `vim.highlight.user` (see below)
+
+vim.highlight.priorities *vim.highlight.priorities*
+
+ Table with default priorities used for highlighting:
+ • `syntax`: `50`, used for standard syntax highlighting
+ • `treesitter`: `100`, used for tree-sitter-based highlighting
+ • `diagnostics`: `150`, used for code analysis such as diagnostics
+ • `user`: `200`, used for user-triggered highlights such as LSP
+ document symbols or `on_yank` autocommands
------------------------------------------------------------------------------
VIM.REGEX *lua-regex*
@@ -1763,6 +1783,13 @@ Lua module: ui *lua-ui*
input({opts}, {on_confirm}) *vim.ui.input()*
Prompts the user for input
+ Example: >
+
+ vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
+ vim.o.shiftwidth = tonumber(input)
+ end)
+<
+
Parameters: ~
{opts} table Additional options. See |input()|
• prompt (string|nil) Text of the prompt.
@@ -1786,6 +1813,22 @@ select({items}, {opts}, {on_choice}) *vim.ui.select()*
Prompts the user to pick a single item from a collection of
entries
+ Example: >
+
+ vim.ui.select({ 'tabs', 'spaces' }, {
+ prompt = 'Select tabs or spaces:',
+ format_item = function(item)
+ return "I'd like to choose " .. item
+ end,
+ }, function(choice)
+ if choice == 'spaces' then
+ vim.o.expandtab = true
+ else
+ vim.o.expandtab = false
+ end
+ end)
+<
+
Parameters: ~
{items} table Arbitrary items
{opts} table Additional options
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 2d2795b1ca..358e944261 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -502,7 +502,9 @@ Note: When using mappings for Visual mode, you can use the "'<" mark, which
is the start of the last selected Visual area in the current buffer |'<|.
The |:filter| command can be used to select what mappings to list. The
-pattern is matched against the {lhs} and {rhs} in the raw form.
+pattern is matched against the {lhs} and {rhs} in the raw form. If a
+description was added using |nvim_set_keymap()| or |nvim_buf_set_keymap()|
+then the pattern is also matched against it.
*:map-verbose*
When 'verbose' is non-zero, listing a key map will also display where it was
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 6e2bc228d0..2f76cc018c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3040,7 +3040,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'guitablabel'* *'gtl'*
'guitablabel' 'gtl' string (default empty)
global
- When nonempty describes the text to use in a label of the GUI tab
+ When non-empty describes the text to use in a label of the GUI tab
pages line. When empty and when the result is empty Vim will use a
default label. See |setting-guitablabel| for more info.
@@ -3057,7 +3057,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'guitabtooltip'* *'gtt'*
'guitabtooltip' 'gtt' string (default empty)
global
- When nonempty describes the text to use in a tooltip for the GUI tab
+ When non-empty describes the text to use in a tooltip for the GUI tab
pages line. When empty Vim will use a default tooltip.
This option is otherwise just like 'guitablabel' above.
You can include a line break. Simplest method is to use |:let|: >
@@ -5906,7 +5906,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'statusline'* *'stl'* *E540* *E542*
'statusline' 'stl' string (default empty)
global or local to window |global-local|
- When nonempty, this option determines the content of the status line.
+ When non-empty, this option determines the content of the status line.
Also see |status-line|.
The option consists of printf style '%' items interspersed with
@@ -6222,7 +6222,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'tabline'* *'tal'*
'tabline' 'tal' string (default empty)
global
- When nonempty, this option determines the content of the tab pages
+ When non-empty, this option determines the content of the tab pages
line at the top of the Vim window. When empty Vim will use a default
tab pages line. See |setting-tabline| for more info.
diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt
index 634145da3e..42005b0d78 100644
--- a/runtime/doc/pattern.txt
+++ b/runtime/doc/pattern.txt
@@ -1421,5 +1421,38 @@ Finally, these constructs are unique to Perl:
are suggested to use ":match" for manual matching and
":2match" for another plugin.
+==============================================================================
+11. Fuzzy matching *fuzzy-match*
+
+Fuzzy matching refers to matching strings using a non-exact search string.
+Fuzzy matching will match a string, if all the characters in the search string
+are present anywhere in the string in the same order. Case is ignored. In a
+matched string, other characters can be present between two consecutive
+characters in the search string. If the search string has multiple words, then
+each word is matched separately. So the words in the search string can be
+present in any order in a string.
+
+Fuzzy matching assigns a score for each matched string based on the following
+criteria:
+ - The number of sequentially matching characters.
+ - The number of characters (distance) between two consecutive matching
+ characters.
+ - Matches at the beginning of a word
+ - Matches at a camel case character (e.g. Case in CamelCase)
+ - Matches after a path separator or a hyphen.
+ - The number of unmatched characters in a string.
+The matching string with the highest score is returned first.
+
+For example, when you search for the "get pat" string using fuzzy matching, it
+will match the strings "GetPattern", "PatternGet", "getPattern", "patGetter",
+"getSomePattern", "MatchpatternGet" etc.
+
+The functions |matchfuzzy()| and |matchfuzzypos()| can be used to fuzzy search
+a string in a List of strings. The matchfuzzy() function returns a List of
+matching strings. The matchfuzzypos() functions returns the List of matches,
+the matching positions and the fuzzy match scores.
+
+The "f" flag of `:vimgrep` enables fuzzy matching.
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index bb4d807413..5b68da8be9 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -989,7 +989,7 @@ commands can be combined to create a NewGrep command: >
5.1 using Vim's internal grep
*:vim* *:vimgrep* *E682* *E683*
-:vim[grep][!] /{pattern}/[g][j] {file} ...
+:vim[grep][!] /{pattern}/[g][j][f] {file} ...
Search for {pattern} in the files {file} ... and set
the error list to the matches. Files matching
'wildignore' are ignored; files in 'suffixes' are
@@ -1014,6 +1014,13 @@ commands can be combined to create a NewGrep command: >
updated. With the [!] any changes in the current
buffer are abandoned.
+ 'f' When the 'f' flag is specified, fuzzy string
+ matching is used to find matching lines. In this
+ case, {pattern} is treated as a literal string
+ instead of a regular expression. See
+ |fuzzy-match| for more information about fuzzy
+ matching strings.
+
|QuickFixCmdPre| and |QuickFixCmdPost| are triggered.
A file that is opened for matching may use a buffer
number, but it is reused if possible to avoid
@@ -1042,20 +1049,20 @@ commands can be combined to create a NewGrep command: >
:vimgrep Error *.c
<
*:lv* *:lvimgrep*
-:lv[imgrep][!] /{pattern}/[g][j] {file} ...
+:lv[imgrep][!] /{pattern}/[g][j][f] {file} ...
:lv[imgrep][!] {pattern} {file} ...
Same as ":vimgrep", except the location list for the
current window is used instead of the quickfix list.
*:vimgrepa* *:vimgrepadd*
-:vimgrepa[dd][!] /{pattern}/[g][j] {file} ...
+:vimgrepa[dd][!] /{pattern}/[g][j][f] {file} ...
:vimgrepa[dd][!] {pattern} {file} ...
Just like ":vimgrep", but instead of making a new list
of errors the matches are appended to the current
list.
*:lvimgrepa* *:lvimgrepadd*
-:lvimgrepa[dd][!] /{pattern}/[g][j] {file} ...
+:lvimgrepa[dd][!] /{pattern}/[g][j][f] {file} ...
:lvimgrepa[dd][!] {pattern} {file} ...
Same as ":vimgrepadd", except the location list for
the current window is used instead of the quickfix
@@ -1332,12 +1339,17 @@ Basic items
%f file name (finds a string)
%o module name (finds a string)
%l line number (finds a number)
+ %e end line number (finds a number)
%c column number (finds a number representing character
column of the error, byte index, a <tab> is 1
character column)
%v virtual column number (finds a number representing
screen column of the error (1 <tab> == 8 screen
columns))
+ %k end column number (finds a number representing
+ the character column of the error, byte index, or a
+ number representing screen end column of the error if
+ it's used with %v)
%t error type (finds a single character):
e - error message
w - warning message
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index c7481ad290..05529dc90a 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -253,21 +253,22 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
below "plugin", just like with plugins in
'runtimepath'.
- If the filetype detection was not enabled yet (this
+ If the filetype detection was already enabled (this
is usually done with a "syntax enable" or "filetype
- on" command in your .vimrc file), this will also look
+ on" command in your |init.vim|, or automatically during
+ |initialization|), and the package was found in
+ "pack/*/opt/{name}", this command will also look
for "{name}/ftdetect/*.vim" files.
When the optional ! is added no plugin files or
ftdetect scripts are loaded, only the matching
directories are added to 'runtimepath'. This is
- useful in your .vimrc. The plugins will then be
- loaded during initialization, see |load-plugins| (note
+ useful in your |init.vim|. The plugins will then be
+ loaded during |initialization|, see |load-plugins| (note
that the loading order will be reversed, because each
- directory is inserted before others).
- Note that for ftdetect scripts to be loaded
- you will need to write `filetype plugin indent on`
- AFTER all `packadd!` commands.
+ directory is inserted before others). In this case, the
+ ftdetect scripts will be loaded during |initialization|,
+ before the |load-plugins| step.
Also see |pack-add|.
@@ -465,8 +466,8 @@ flag when defining the function, it is not relevant when executing it. >
:set cpo-=C
<
*line-continuation-comment*
-To add a comment in between the lines start with '\" '. Notice the space
-after the double quote. Example: >
+To add a comment in between the lines start with '"\ '. Notice the space
+after the backslash. Example: >
let array = [
"\ first entry comment
\ 'first',
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 5829dbdd6b..dbd8ec6fef 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -14,10 +14,12 @@ VIM.TREESITTER *lua-treesitter*
Nvim integrates the tree-sitter library for incremental parsing of buffers.
*vim.treesitter.language_version*
-To check which language version is compiled with neovim, the number is stored
-within `vim.treesitter.language_version`. This number is not too helpful
-unless you are wondering about compatibility between different versions of
-compiled grammars.
+The latest parser ABI version that is supported by the bundled tree-sitter
+library.
+
+ *vim.treesitter.minimum_language_version*
+The earliest parser ABI version that is supported by the bundled tree-sitter
+library.
Parser files *treesitter-parsers*
@@ -49,10 +51,10 @@ Whenever you need to access the current syntax tree, parse the buffer: >
tstree = parser:parse()
-<This will return a table of immutable trees that represent the current state of the
-buffer. When the plugin wants to access the state after a (possible) edit
-it should call `parse()` again. If the buffer wasn't edited, the same tree will
-be returned again without extra work. If the buffer was parsed before,
+<This will return a table of immutable trees that represent the current state
+of the buffer. When the plugin wants to access the state after a (possible)
+edit it should call `parse()` again. If the buffer wasn't edited, the same tree
+will be returned again without extra work. If the buffer was parsed before,
incremental parsing will be done of the changed parts.
Note: to use the parser directly inside a |nvim_buf_attach| Lua callback, you
@@ -61,9 +63,10 @@ parsing shouldn't be done directly in the change callback anyway as they will
be very frequent. Rather a plugin that does any kind of analysis on a tree
should use a timer to throttle too frequent updates.
-tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()*
+tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()*
Changes the regions the parser should consider. This is used for
- language injection. {region_list} should be of the form (all zero-based): >
+ language injection. {region_list} should be of the form
+ (all zero-based): >
{
{node1, node2},
...
@@ -92,13 +95,13 @@ tsnode:next_sibling() *tsnode:next_sibling()*
tsnode:prev_sibling() *tsnode:prev_sibling()*
Get the node's previous sibling.
-tsnode:next_named_sibling() *tsnode:next_named_sibling()*
+tsnode:next_named_sibling() *tsnode:next_named_sibling()*
Get the node's next named sibling.
-tsnode:prev_named_sibling() *tsnode:prev_named_sibling()*
+tsnode:prev_named_sibling() *tsnode:prev_named_sibling()*
Get the node's previous named sibling.
-tsnode:iter_children() *tsnode:iter_children()*
+tsnode:iter_children() *tsnode:iter_children()*
Iterates over all the direct children of {tsnode}, regardless of
whether they are named or not.
Returns the child node plus the eventual field name corresponding to
@@ -114,10 +117,10 @@ tsnode:child({index}) *tsnode:child()*
Get the node's child at the given {index}, where zero represents the
first child.
-tsnode:named_child_count() *tsnode:named_child_count()*
+tsnode:named_child_count() *tsnode:named_child_count()*
Get the node's number of named children.
-tsnode:named_child({index}) *tsnode:named_child()*
+tsnode:named_child({index}) *tsnode:named_child()*
Get the node's named child at the given {index}, where zero represents
the first named child.
@@ -157,20 +160,20 @@ tsnode:sexpr() *tsnode:sexpr()*
tsnode:id() *tsnode:id()*
Get an unique identifier for the node inside its own tree.
- No guarantees are made about this identifier's internal representation,
- except for being a primitive lua type with value equality (so not a table).
- Presently it is a (non-printable) string.
+ No guarantees are made about this identifier's internal
+ representation, except for being a primitive lua type with value
+ equality (so not a table). Presently it is a (non-printable) string.
Note: the id is not guaranteed to be unique for nodes from different
trees.
+ *tsnode:descendant_for_range()*
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:descendant_for_range()*
Get the smallest node within this node that spans the given range of
(row, column) positions
+ *tsnode:named_descendant_for_range()*
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:named_descendant_for_range()*
Get the smallest named node within this node that spans the given
range of (row, column) positions
@@ -192,11 +195,11 @@ and predicates. A `capture` allows you to associate names with a specific
node in a pattern. A `predicate` adds arbitrary metadata and conditional data
to a match.
-Treesitter Query Predicates *lua-treesitter-predicates*
+Treesitter Query Predicates *lua-treesitter-predicates*
When writing queries for treesitter, one might use `predicates`, that is,
-special scheme nodes that are evaluated to verify things on a captured node for
-example, the |eq?| predicate : >
+special scheme nodes that are evaluated to verify things on a captured node
+for example, the |eq?| predicate : >
((identifier) @foo (#eq? @foo "foo"))
This will only match identifier corresponding to the `"foo"` text.
@@ -209,24 +212,24 @@ Here is a list of built-in predicates :
((node1) @left (node2) @right (#eq? @left @right))
<
`match?` *ts-predicate-match?*
- `vim-match?` *ts-predicate-vim-match?*
+ `vim-match?` *ts-predicate-vim-match?*
This will match if the provided vim regex matches the text
corresponding to a node : >
((identifier) @constant (#match? @constant "^[A-Z_]+$"))
< Note: the `^` and `$` anchors will respectively match the
start and end of the node's text.
- `lua-match?` *ts-predicate-lua-match?*
+ `lua-match?` *ts-predicate-lua-match?*
This will match the same way than |match?| but using lua
regexes.
- `contains?` *ts-predicate-contains?*
+ `contains?` *ts-predicate-contains?*
Will check if any of the following arguments appears in the
text corresponding to the node : >
((identifier) @foo (#contains? @foo "foo"))
((identifier) @foo-bar (#contains @foo-bar "foo" "bar"))
<
- `any-of?` *ts-predicate-any-of?*
+ `any-of?` *ts-predicate-any-of?*
Will check if the text is the same as any of the following.
This is the recommended way to check if the node matches one
of many keywords for example, as it has been optimized for
@@ -234,27 +237,27 @@ Here is a list of built-in predicates :
arguments : >
((identifier) @foo (#any-of? @foo "foo" "bar"))
<
- *lua-treesitter-not-predicate*
+ *lua-treesitter-not-predicate*
Each predicate has a `not-` prefixed predicate that is just the negation of
the predicate.
- *vim.treesitter.query.add_predicate()*
+ *vim.treesitter.query.add_predicate()*
vim.treesitter.query.add_predicate({name}, {handler})
This adds a predicate with the name {name} to be used in queries.
{handler} should be a function whose signature will be : >
handler(match, pattern, bufnr, predicate)
<
- *vim.treesitter.query.list_predicates()*
+ *vim.treesitter.query.list_predicates()*
vim.treesitter.query.list_predicates()
This lists the currently available predicates to use in queries.
-Treesitter Query Directive *lua-treesitter-directives*
+Treesitter Query Directive *lua-treesitter-directives*
-Treesitter queries can also contain `directives`. Directives store metadata for a node
-or match and perform side effects. For example, the |set!| predicate sets metadata on
-the match or node : >
+Treesitter queries can also contain `directives`. Directives store metadata
+for a node or match and perform side effects. For example, the |set!|
+predicate sets metadata on the match or node : >
((identifier) @foo (#set! "type" "parameter"))
Here is a list of built-in directives:
@@ -268,8 +271,8 @@ Here is a list of built-in directives:
Takes the range of the captured node and applies the offsets
to it's range : >
((identifier) @constant (#offset! @constant 0 1 0 -1))
-< This will generate a range object for the captured node with the
- offsets applied. The arguments are
+< This will generate a range object for the captured node with
+ the offsets applied. The arguments are
`({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
The default key is "offset".
@@ -279,25 +282,25 @@ vim.treesitter.query.add_directive({name}, {handler})
This adds a directive with the name {name} to be used in queries.
{handler} should be a function whose signature will be : >
handler(match, pattern, bufnr, predicate, metadata)
-Handlers can set match level data by setting directly on the metadata object `metadata.key = value`
-Handlers can set node level data by using the capture id on the metadata table
-`metadata[capture_id].key = value`
+Handlers can set match level data by setting directly on the metadata object
+`metadata.key = value` Handlers can set node level data by using the capture
+id on the metadata table `metadata[capture_id].key = value`
*vim.treesitter.query.list_directives()*
vim.treesitter.query.list_directives()
This lists the currently available directives to use in queries.
-Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
+Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
NOTE: This is a partially implemented feature, and not usable as a default
solution yet. What is documented here is a temporary interface intended
for those who want to experiment with this feature and contribute to
its development.
-Highlights are defined in the same query format as in the tree-sitter highlight
-crate, with some limitations and additions. Set a highlight query for a
-buffer with this code: >
+Highlights are defined in the same query format as in the tree-sitter
+highlight crate, with some limitations and additions. Set a highlight query
+for a buffer with this code: >
local query = [[
"for" @keyword
@@ -338,7 +341,8 @@ Treesitter Highlighting Priority *lua-treesitter-highlight-priority*
Tree-sitter uses |nvim_buf_set_extmark()| to set highlights with a default
priority of 100. This enables plugins to set a highlighting priority lower or
higher than tree-sitter. It is also possible to change the priority of an
-individual query pattern manually by setting its `"priority"` metadata attribute: >
+individual query pattern manually by setting its `"priority"` metadata
+attribute: >
(
(super_important_node) @ImportantHighlight
@@ -461,7 +465,7 @@ parse_query({lang}, {query}) *parse_query()*
can be used to search nodes in the syntax tree for the
patterns defined in {query} using `iter_*` methods below.
- Exposes `info` and `captures` with additional information about the {query}.
+ Exposes `info` and `captures` with additional context about {query}.
• `captures` contains the list of unique capture names defined
in {query}. - `info.captures` also points to `captures` .
• `info.patterns` contains information about predicates.
@@ -609,10 +613,9 @@ LanguageTree:children({self}) *LanguageTree:children()*
{self}
LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
- Determines whether This goes down the tree to recursively check children.
+ Determines whether {range} is contained in this language tree
- Parameters: ~
- {range} is contained in this language tree
+ This goes down the tree to recursively check children.
Parameters: ~
{range} A range, that is a `{ start_line, start_col,
@@ -622,8 +625,9 @@ LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
LanguageTree:destroy({self}) *LanguageTree:destroy()*
Destroys this language tree and all its children.
- Any cleanup logic should be performed here. Note, this DOES
- NOT remove this tree from a parent. `remove_child` must be called on the parent to remove it.
+ Any cleanup logic should be performed here.
+
+ Note: This DOES NOT remove this tree from a parent. Instead, `remove_child` must be called on the parent to remove it.
Parameters: ~
{self}
@@ -666,7 +670,8 @@ LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
{self}
LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
- Determines whether this tree is valid. If the tree is invalid, `parse()` must be called to get the updated tree.
+ Determines whether this tree is valid. If the tree is invalid,
+ call `parse()` . This will return the updated tree.
Parameters: ~
{self}
@@ -679,7 +684,7 @@ LanguageTree:lang({self}) *LanguageTree:lang()*
*LanguageTree:language_for_range()*
LanguageTree:language_for_range({self}, {range})
- Gets the appropriate language that contains
+ Gets the appropriate language that contains {range}
Parameters: ~
{range} A text range, see |LanguageTree:contains|
@@ -695,13 +700,22 @@ LanguageTree:parse({self}) *LanguageTree:parse()*
{self}
LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
- Registers callbacks for the parser
-
- Parameters: ~
- {cbs} An `nvim_buf_attach` -like table argument with the following keys : `on_bytes` : see `nvim_buf_attach` , but this will be called after the parsers callback. `on_changedtree` : a callback that will be called every time the
- tree has syntactical changes. it will only be
- passed one argument, that is a table of the ranges
- (as node ranges) that changed. `on_child_added` : emitted when a child is added to the tree. `on_child_removed` : emitted when a child is removed from the tree.
+ Registers callbacks for the parser.
+
+ Parameters: ~
+ {cbs} table An |nvim_buf_attach()|-like table argument
+ with the following keys :
+ • `on_bytes` : see |nvim_buf_attach()|, but this will be
+ called after the parsers callback.
+ • `on_changedtree` : a callback that will be
+ called every time the tree has syntactical
+ changes. It will only be passed one argument,
+ which is a table of the ranges (as node ranges)
+ that changed.
+ • `on_child_added` : emitted when a child is added
+ to the tree.
+ • `on_child_removed` : emitted when a child is
+ removed from the tree.
{self}
LanguageTree:remove_child({self}, {lang}) *LanguageTree:remove_child()*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index bf29c94d51..bf024315f6 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -608,6 +608,8 @@ String manipulation: *string-functions*
toupper() turn a string to uppercase
match() position where a pattern matches in a string
matchend() position where a pattern match ends in a string
+ matchfuzzy() fuzzy matches a string in a list of strings
+ matchfuzzypos() fuzzy matches a string in a list of strings
matchstr() match of a pattern in a string
matchstrpos() match and positions of a pattern in a string
matchlist() like matchstr() and also return submatches
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index fc0230c62d..38869f8e94 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -388,6 +388,8 @@ g8 Print the hex values of the bytes used in the
|:marks| - filter by text in the current file,
or file name for other files
|:oldfiles| - filter by file name
+ |:registers| - filter by register contents
+ (does not work multi-line)
|:set| - filter by option name
Only normal messages are filtered, error messages are
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 11849632c5..5ea6a9c5dd 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -322,6 +322,8 @@ coerced to strings. See |id()| for more details, currently it uses
|c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>.
+|CursorMoved| always triggers when moving between windows.
+
Lua interface (|lua.txt|):
- `:lua print("a\0b")` will print `a^@b`, like with `:echomsg "a\nb"` . In Vim
@@ -431,7 +433,8 @@ Vimscript compatibility:
`this_session` does not alias to |v:this_session|
Working directory (Vim implemented some of these later than Nvim):
-- |DirChanged| can be triggered when switching to another window.
+- |DirChanged| and |DirChangedPre| can be triggered when switching to another
+ window or tab.
- |getcwd()| and |haslocaldir()| may throw errors if the tab page or window
cannot be found. *E5000* *E5001* *E5002*
- |haslocaldir()| checks for tab-local directory if and only if -1 is passed as
@@ -483,7 +486,6 @@ Commands:
:tearoff
Compile-time features:
- EBCDIC
Emacs tags support
X11 integration (see |x11-selection|)
@@ -492,6 +494,9 @@ Eval:
*js_encode()*
*js_decode()*
*v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead.
+ *v:sizeofint*
+ *v:sizeoflong*
+ *v:sizeofpointer*
Events:
*SigUSR1* Use |Signal| to detect `SIGUSR1` signal instead.
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index 5563a56216..4d5366a41a 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -255,6 +255,7 @@ Additionally the following commands can be used:
X delete (2) |v_X|
Y yank (2) |v_Y|
p put |v_p|
+ P put without unnamed register overwrite |v_P|
J join (1) |v_J|
U make uppercase |v_U|
u make lowercase |v_u|
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 8b40b43a04..f47b3ee0d4 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -44,7 +44,7 @@ endif
" file name matches ft_ignore_pat.
" When using this, the entry should probably be further down below with the
" other StarSetf() calls.
-func! s:StarSetf(ft)
+func s:StarSetf(ft)
if expand("<amatch>") !~ g:ft_ignore_pat
exe 'setf ' . a:ft
endif
@@ -225,6 +225,9 @@ au BufNewFile,BufRead *.bib setf bib
" BibTeX Bibliography Style
au BufNewFile,BufRead *.bst setf bst
+" Bicep
+au BufNewFile,BufRead *.bicep setf bicep
+
" BIND configuration
" sudoedit uses namedXXXX.conf
au BufNewFile,BufRead named*.conf,rndc*.conf,rndc*.key setf named
@@ -723,6 +726,10 @@ au BufNewFile,BufRead gnashrc,.gnashrc,gnashpluginrc,.gnashpluginrc setf gnash
au BufNewFile,BufRead gitolite.conf setf gitolite
au BufNewFile,BufRead {,.}gitolite.rc,example.gitolite.rc setf perl
+" Glimmer-flavored TypeScript and JavaScript
+au BufNewFile,BufRead *.gts setf typescript.glimmer
+au BufNewFile,BufRead *.gjs setf javascript.glimmer
+
" Gnuplot scripts
au BufNewFile,BufRead *.gpi,.gnuplot setf gnuplot
@@ -1543,6 +1550,9 @@ au BufNewFile,BufRead *.r,*.R call dist#ft#FTr()
" Remind
au BufNewFile,BufRead .reminders,*.remind,*.rem setf remind
+" ReScript
+au BufNewFile,BufRead *.res,*.resi setf rescript
+
" Resolv.conf
au BufNewFile,BufRead resolv.conf setf resolv
@@ -1785,6 +1795,9 @@ au BufNewFile,BufRead *.mib,*.my setf mib
au BufNewFile,BufRead *.hog,snort.conf,vision.conf setf hog
au BufNewFile,BufRead *.rules call dist#ft#FTRules()
+" Solidity
+au BufRead,BufNewFile *.sol setf solidity
+
" SPARQL queries
au BufNewFile,BufRead *.rq,*.sparql setf sparql
@@ -2514,7 +2527,7 @@ endif
" Function called for testing all functions defined here. These are
" script-local, thus need to be executed here.
" Returns a string with error messages (hopefully empty).
-func! TestFiletypeFuncs(testlist)
+func TestFiletypeFuncs(testlist)
let output = ''
for f in a:testlist
try
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index b4537c2882..fcb1e61764 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -447,7 +447,7 @@ local function set_list(loclist, opts)
vim.fn.setqflist({}, ' ', { title = title, items = items })
end
if open then
- vim.api.nvim_command(loclist and "lopen" or "copen")
+ vim.api.nvim_command(loclist and "lopen" or "botright copen")
end
end
@@ -920,7 +920,8 @@ M.handlers.underline = {
underline_ns,
higroup,
{ diagnostic.lnum, diagnostic.col },
- { diagnostic.end_lnum, diagnostic.end_col }
+ { diagnostic.end_lnum, diagnostic.end_col },
+ { priority = vim.highlight.priorities.diagnostics }
)
end
save_extmarks(underline_ns, bufnr)
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index e2cf408f3b..0555b87651 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -65,6 +65,7 @@ local extension = {
bdf = "bdf",
beancount = "beancount",
bib = "bib",
+ bicep = "bicep",
bl = "blank",
bsdl = "bsdl",
bst = "bst",
@@ -246,6 +247,8 @@ local extension = {
gradle = "groovy",
groovy = "groovy",
gsp = "gsp",
+ gjs = "javascript.glimmer",
+ gts = "typescript.glimmer",
hack = "hack",
hackpartial = "hack",
haml = "haml",
@@ -514,6 +517,8 @@ local extension = {
rego = "rego",
rem = "remind",
remind = "remind",
+ res = "rescript",
+ resi = "rescript",
frt = "reva",
testUnit = "rexx",
rex = "rexx",
@@ -595,6 +600,7 @@ local extension = {
sl = "slang",
ice = "slice",
score = "slrnsc",
+ sol = "solidity",
tpl = "smarty",
ihlp = "smcl",
smcl = "smcl",
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 12faa0a6e1..4105ef0675 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,9 +1,16 @@
local api = vim.api
-local highlight = {}
+local M = {}
+
+M.priorities = {
+ syntax = 50,
+ treesitter = 100,
+ diagnostics = 150,
+ user = 200,
+}
---@private
-function highlight.create(higroup, hi_info, default)
+function M.create(higroup, hi_info, default)
local options = {}
-- TODO: Add validation
for k, v in pairs(hi_info) do
@@ -13,28 +20,33 @@ function highlight.create(higroup, hi_info, default)
end
---@private
-function highlight.link(higroup, link_to, force)
+function M.link(higroup, link_to, force)
vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
end
-
--- Highlight range between two positions
---
---@param bufnr number of buffer to apply highlighting to
---@param ns namespace to add highlight to
---@param higroup highlight group to use for highlighting
----@param rtype type of range (:help setreg, default charwise)
----@param inclusive boolean indicating whether the range is end-inclusive (default false)
----@param priority number indicating priority of highlight (default 50)
-function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive, priority)
- rtype = rtype or 'v'
- inclusive = inclusive or false
- priority = priority or 50
+---@param start first position (tuple {line,col})
+---@param finish second position (tuple {line,col})
+---@param opts table with options:
+-- - regtype type of range (:help setreg, default charwise)
+-- - inclusive boolean indicating whether the range is end-inclusive (default false)
+-- - priority number indicating priority of highlight (default priorities.user)
+function M.range(bufnr, ns, higroup, start, finish, opts)
+ opts = opts or {}
+ local regtype = opts.regtype or "v"
+ local inclusive = opts.inclusive or false
+ local priority = opts.priority or M.priorities.user
-- sanity check
- if start[2] < 0 or finish[1] < start[1] then return end
+ if start[2] < 0 or finish[1] < start[1] then
+ return
+ end
- local region = vim.region(bufnr, start, finish, rtype, inclusive)
+ local region = vim.region(bufnr, start, finish, regtype, inclusive)
for linenr, cols in pairs(region) do
local end_row
if cols[2] == -1 then
@@ -46,13 +58,12 @@ function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive, pr
end_row = end_row,
end_col = cols[2],
priority = priority,
- strict = false
+ strict = false,
})
end
-
end
-local yank_ns = api.nvim_create_namespace('hlyank')
+local yank_ns = api.nvim_create_namespace("hlyank")
--- Highlight the yanked region
---
--- use from init.vim via
@@ -62,26 +73,40 @@ local yank_ns = api.nvim_create_namespace('hlyank')
--- customize conditions (here: do not highlight a visual selection) via
--- au TextYankPost * lua vim.highlight.on_yank {on_visual=false}
---
--- @param opts dictionary with options controlling the highlight:
+-- @param opts table with options controlling the highlight:
-- - higroup highlight group for yanked region (default "IncSearch")
-- - timeout time in ms before highlight is cleared (default 150)
-- - on_macro highlight when executing macro (default false)
-- - on_visual highlight when yanking visual selection (default true)
-- - event event structure (default vim.v.event)
-function highlight.on_yank(opts)
- vim.validate {
- opts = { opts,
- function(t) if t == nil then return true else return type(t) == 'table' end end,
- 'a table or nil to configure options (see `:h highlight.on_yank`)',
- }}
+function M.on_yank(opts)
+ vim.validate({
+ opts = {
+ opts,
+ function(t)
+ if t == nil then
+ return true
+ else
+ return type(t) == "table"
+ end
+ end,
+ "a table or nil to configure options (see `:h highlight.on_yank`)",
+ },
+ })
opts = opts or {}
local event = opts.event or vim.v.event
local on_macro = opts.on_macro or false
local on_visual = (opts.on_visual ~= false)
- if (not on_macro) and vim.fn.reg_executing() ~= '' then return end
- if event.operator ~= 'y' or event.regtype == '' then return end
- if (not on_visual) and event.visual then return end
+ if not on_macro and vim.fn.reg_executing() ~= "" then
+ return
+ end
+ if event.operator ~= "y" or event.regtype == "" then
+ return
+ end
+ if not on_visual and event.visual then
+ return
+ end
local higroup = opts.higroup or "IncSearch"
local timeout = opts.timeout or 150
@@ -92,19 +117,23 @@ function highlight.on_yank(opts)
local pos1 = vim.fn.getpos("'[")
local pos2 = vim.fn.getpos("']")
- pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
- pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
-
- highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive, 200)
+ pos1 = { pos1[2] - 1, pos1[3] - 1 + pos1[4] }
+ pos2 = { pos2[2] - 1, pos2[3] - 1 + pos2[4] }
- vim.defer_fn(
- function()
- if api.nvim_buf_is_valid(bufnr) then
- api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
- end
- end,
- timeout
+ M.range(
+ bufnr,
+ yank_ns,
+ higroup,
+ pos1,
+ pos2,
+ { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user }
)
+
+ vim.defer_fn(function()
+ if api.nvim_buf_is_valid(bufnr) then
+ api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+ end
+ end, timeout)
end
-return highlight
+return M
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 37e222a5ce..8d11b4621c 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1598,7 +1598,7 @@ end
local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil
for _, item in pairs(items) do
- if item.textEdit and item.textEdit.range.start.line == lnum - 1 then
+ if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then
if min_start_char and min_start_char ~= item.textEdit.range.start.character then
return nil
end
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index a997b887d9..f5aefd4402 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -336,7 +336,7 @@ local function location_handler(_, result, ctx, _)
title = 'LSP locations',
items = util.locations_to_items(result, client.offset_encoding)
})
- api.nvim_command("copen")
+ api.nvim_command("botright copen")
end
else
util.jump_to_location(result, client.offset_encoding)
@@ -430,7 +430,7 @@ local make_call_hierarchy_handler = function(direction)
end
end
vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items})
- api.nvim_command("copen")
+ api.nvim_command("botright copen")
end
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index d22c00ae76..655c3a4679 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1551,9 +1551,7 @@ do --[[ References ]]
document_highlight_kind[kind],
{ start_line, start_idx },
{ end_line, end_idx },
- nil,
- false,
- 40)
+ { priority = vim.highlight.priorities.user })
end
end
end
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 22b528838c..b6f61cfb2e 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -22,7 +22,21 @@ local _link_default_highlight_once = function(from, to)
return from
end
-TSHighlighter.hl_map = {
+-- If @definition.special does not exist use @definition instead
+local subcapture_fallback = {
+ __index = function(self, capture)
+ local rtn
+ local shortened = capture
+ while not rtn and shortened do
+ shortened = shortened:match('(.*)%.')
+ rtn = shortened and rawget(self, shortened)
+ end
+ rawset(self, capture, rtn or "__notfound")
+ return rtn
+ end
+}
+
+TSHighlighter.hl_map = setmetatable({
["error"] = "Error",
-- Miscs
@@ -66,7 +80,7 @@ TSHighlighter.hl_map = {
["type.builtin"] = "Type",
["structure"] = "Structure",
["include"] = "Include",
-}
+}, subcapture_fallback)
---@private
local function is_highlight_name(capture_name)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 85fd5cd8e0..b83df65151 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -76,8 +76,8 @@ function LanguageTree:lang()
end
--- Determines whether this tree is valid.
---- If the tree is invalid, `parse()` must be called
---- to get the updated tree.
+--- If the tree is invalid, call `parse()`.
+--- This will return the updated tree.
function LanguageTree:is_valid()
return self._valid
end
@@ -234,7 +234,9 @@ end
--- Destroys this language tree and all its children.
---
--- Any cleanup logic should be performed here.
---- Note, this DOES NOT remove this tree from a parent.
+---
+--- Note:
+--- This DOES NOT remove this tree from a parent. Instead,
--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
@@ -448,14 +450,14 @@ function LanguageTree:_on_detach(...)
self:_do_callback('detach', ...)
end
---- Registers callbacks for the parser
----@param cbs An `nvim_buf_attach`-like table argument with the following keys :
---- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
---- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
---- it will only be passed one argument, that is a table of the ranges (as node ranges) that
---- changed.
---- `on_child_added` : emitted when a child is added to the tree.
---- `on_child_removed` : emitted when a child is removed from the tree.
+--- Registers callbacks for the parser.
+---@param cbs table An |nvim_buf_attach()|-like table argument with the following keys :
+--- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback.
+--- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
+--- It will only be passed one argument, which is a table of the ranges (as node ranges) that
+--- changed.
+--- - `on_child_added` : emitted when a child is added to the tree.
+--- - `on_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs)
if not cbs then return end
@@ -493,7 +495,7 @@ local function tree_contains(tree, range)
return false
end
---- Determines whether @param range is contained in this language tree
+--- Determines whether {range} is contained in this language tree
---
--- This goes down the tree to recursively check children.
---
@@ -508,7 +510,7 @@ function LanguageTree:contains(range)
return false
end
---- Gets the appropriate language that contains @param range
+--- Gets the appropriate language that contains {range}
---
---@param range A text range, see |LanguageTree:contains|
function LanguageTree:language_for_range(range)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index b3036ea679..8383551b5f 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -146,13 +146,13 @@ local query_cache = setmetatable({}, {
})
--- Parse {query} as a string. (If the query is in a file, the caller
---- should read the contents into a string before calling).
+--- should read the contents into a string before calling).
---
--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
--- search nodes in the syntax tree for the patterns defined in {query}
--- using `iter_*` methods below.
---
---- Exposes `info` and `captures` with additional information about the {query}.
+--- Exposes `info` and `captures` with additional context about {query}.
--- - `captures` contains the list of unique capture names defined in
--- {query}.
--- -` info.captures` also points to `captures`.
@@ -199,11 +199,13 @@ function M.get_node_text(node, source)
lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
end
- if #lines == 1 then
- lines[1] = string.sub(lines[1], start_col+1, end_col)
- else
- lines[1] = string.sub(lines[1], start_col+1)
- lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ if #lines > 0 then
+ if #lines == 1 then
+ lines[1] = string.sub(lines[1], start_col+1, end_col)
+ else
+ lines[1] = string.sub(lines[1], start_col+1)
+ lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ end
end
return table.concat(lines, "\n")
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 0f2de6ce5c..9d4b38f08a 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -78,7 +78,7 @@ end
---
--- Example:
--- <pre>
---- vim.ui.input({ prompt = 'Select value for shiftwidth: ' }, function(input)
+--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
--- vim.o.shiftwidth = tonumber(input)
--- end)
--- </pre>
diff --git a/runtime/syntax/structurizr.vim b/runtime/syntax/structurizr.vim
index 73629b1495..ab9e4ee609 100644
--- a/runtime/syntax/structurizr.vim
+++ b/runtime/syntax/structurizr.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Structurizr DSL
" Maintainer: Bastian Venthur <venthur@debian.org>
-" Last Change: 2021-08-16
+" Last Change: 2022-02-15
" Remark: For a language reference, see
" https://github.com/structurizr/dsl
@@ -30,6 +30,7 @@ syn keyword skeyword deployment
syn keyword skeyword deploymentenvironment
syn keyword skeyword deploymentgroup
syn keyword skeyword deploymentnode
+syn keyword skeyword description
syn keyword skeyword dynamic
syn keyword skeyword element
syn keyword skeyword enterprise
@@ -37,7 +38,6 @@ syn keyword skeyword exclude
syn keyword skeyword filtered
syn keyword skeyword group
syn keyword skeyword healthcheck
-syn keyword skeyword impliedrelationships
syn keyword skeyword include
syn keyword skeyword infrastructurenode
syn keyword skeyword model
@@ -51,6 +51,7 @@ syn keyword skeyword styles
syn keyword skeyword systemcontext
syn keyword skeyword systemlandscape
syn keyword skeyword tags
+syn keyword skeyword technology
syn keyword skeyword terminology
syn keyword skeyword theme
syn keyword skeyword title
@@ -63,7 +64,11 @@ syn match skeyword "\!adrs\s\+"
syn match skeyword "\!constant\s\+"
syn match skeyword "\!docs\s\+"
syn match skeyword "\!identifiers\s\+"
+syn match skeyword "\!impliedrelationships\s\+"
syn match skeyword "\!include\s\+"
+syn match skeyword "\!plugin\s\+"
+syn match skeyword "\!ref\s\+"
+syn match skeyword "\!script\s\+"
syn region sstring oneline start='"' end='"'
diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua
index 0a7da4d4ef..6871858a0b 100644
--- a/scripts/lintcommit.lua
+++ b/scripts/lintcommit.lua
@@ -94,10 +94,24 @@ local function validate_commit(commit_message)
return [[Description ends with a period (".").]]
end
- -- Check that description has exactly one whitespace after colon, followed by
- -- a lowercase letter and then any number of letters.
- if not string.match(after_colon, '^ %l%a*') then
- return [[There should be one whitespace after the colon and the first letter should lowercase.]]
+ -- Check that description starts with a whitespace.
+ if after_colon:sub(1,1) ~= " " then
+ return [[There should be a whitespace after the colon.]]
+ end
+
+ -- Check that description doesn't start with multiple whitespaces.
+ if after_colon:sub(1,2) == " " then
+ return [[There should only be one whitespace after the colon.]]
+ end
+
+ -- Check that first character after space isn't uppercase.
+ if string.match(after_colon:sub(2,2), '%u') then
+ return [[First character should not be uppercase.]]
+ end
+
+ -- Check that description isn't just whitespaces
+ if vim.trim(after_colon) == "" then
+ return [[Description shouldn't be empty.]]
end
return nil
@@ -168,12 +182,14 @@ function M._test()
['ci(tui)!: message with scope and breaking change'] = true,
['vim-patch:8.2.3374: Pyret files are not recognized (#15642)'] = true,
['vim-patch:8.1.1195,8.2.{3417,3419}'] = true,
+ ['revert: "ci: use continue-on-error instead of "|| true""'] = true,
[':no type before colon 1'] = false,
[' :no type before colon 2'] = false,
[' :no type before colon 3'] = false,
['ci(empty description):'] = false,
- ['ci(whitespace as description): '] = false,
+ ['ci(only whitespace as description): '] = false,
['docs(multiple whitespaces as description): '] = false,
+ ['revert(multiple whitespaces and then characters as description): description'] = false,
['ci no colon after type'] = false,
['test: extra space after colon'] = false,
['ci: tab after colon'] = false,
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 1c265f0f40..67a2cc96fd 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -576,13 +576,13 @@ show_vimpatches() {
runtime_commits[$commit]=1
done
- while read -r vim_commit; do
+ list_missing_vimpatches 1 "$@" | while read -r vim_commit; do
if [[ "${runtime_commits[$vim_commit]-}" ]]; then
printf ' • %s (+runtime)\n' "${vim_commit}"
else
printf ' • %s\n' "${vim_commit}"
fi
- done <<< "$(list_missing_vimpatches 1 "$@")"
+ done
cat << EOF
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 2d5403d4b8..922d288da1 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -287,8 +287,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ start = normalize_index(buf, start, true, &oob);
+ end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
@@ -374,15 +374,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ start = normalize_index(buf, start, true, &oob);
+ end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return;
}
-
if (start > end) {
api_set_error(err,
kErrorTypeValidation,
@@ -554,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
- start_row = normalize_index(buf, start_row, &oob);
+ start_row = normalize_index(buf, start_row, false, &oob);
if (oob || start_row == buf->b_ml.ml_line_count + 1) {
api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
return;
}
- end_row = normalize_index(buf, end_row, &oob);
+ end_row = normalize_index(buf, end_row, false, &oob);
if (oob || end_row == buf->b_ml.ml_line_count + 1) {
api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
return;
@@ -757,6 +756,108 @@ end:
try_end(err);
}
+/// Gets a range from the buffer.
+///
+/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only
+/// portions of a line.
+///
+/// Indexing is zero-based. Column indices are end-exclusive.
+///
+/// Prefer |nvim_buf_get_lines()| when retrieving entire lines.
+///
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param start_row First line index
+/// @param start_col Starting byte offset of first line
+/// @param end_row Last line index
+/// @param end_col Ending byte offset of last line (exclusive)
+/// @param opts Optional parameters. Currently unused.
+/// @param[out] err Error details, if any
+/// @return Array of lines, or empty array for unloaded buffer.
+ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return rv;
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return rv;
+ }
+
+ bool oob = false;
+ start_row = normalize_index(buf, start_row, false, &oob);
+ end_row = normalize_index(buf, end_row, false, &oob);
+
+ if (oob) {
+ api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ return rv;
+ }
+
+ // nvim_buf_get_lines doesn't care if the start row is greater than the end
+ // row (it will just return an empty array), but nvim_buf_get_text does in
+ // order to maintain symmetry with nvim_buf_set_text.
+ if (start_row > end_row) {
+ api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ return rv;
+ }
+
+ bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
+
+ if (start_row == end_row) {
+ String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+
+ ADD(rv, STRING_OBJ(line));
+ return rv;
+ }
+
+ rv.size = (size_t)(end_row - start_row) + 1;
+ rv.items = xcalloc(rv.size, sizeof(Object));
+
+ rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+ if (rv.size > 2) {
+ Array tmp = ARRAY_DICT_INIT;
+ tmp.items = &rv.items[1];
+ if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) {
+ goto end;
+ }
+ }
+
+ rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+end:
+ if (ERROR_SET(err)) {
+ api_free_array(rv);
+ rv.size = 0;
+ rv.items = NULL;
+ }
+
+ return rv;
+}
+
/// Returns the byte offset of a line (0-indexed). |api-indexing|
///
/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
@@ -1386,11 +1487,11 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
}
// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
+static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
{
int64_t line_count = buf->b_ml.ml_line_count;
// Fix if < 0
- index = index < 0 ? line_count + index +1 : index;
+ index = index < 0 ? line_count + index + (int)end_exclusive : index;
// Check for oob
if (index > line_count) {
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 075e2c48d2..f6dce1905e 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -81,10 +81,12 @@ return {
highlight = {
"bold";
"standout";
+ "strikethrough";
"underline";
"undercurl";
"italic";
"reverse";
+ "nocombine";
"default";
"global";
"cterm";
@@ -100,10 +102,12 @@ return {
highlight_cterm = {
"bold";
"standout";
+ "strikethrough";
"underline";
"undercurl";
"italic";
"reverse";
+ "nocombine";
};
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index ddcfff0097..971fa1cb0f 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -411,7 +411,6 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
current_sctx = save_current_sctx;
}
-
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
{
if (buffer == 0) {
@@ -758,6 +757,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr
return true;
}
+/// Returns a substring of a buffer line
+///
+/// @param buf Buffer handle
+/// @param lnum Line number (1-based)
+/// @param start_col Starting byte offset into line (0-based)
+/// @param end_col Ending byte offset into line (0-based, exclusive)
+/// @param replace_nl Replace newlines ('\n') with null ('\0')
+/// @param err Error object
+/// @return The text between start_col and end_col on line lnum of buffer buf
+String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl,
+ Error *err)
+{
+ String rv = STRING_INIT;
+
+ if (lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line index is too high");
+ return rv;
+ }
+
+ const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ size_t line_length = strlen(bufstr);
+
+ start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
+ end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col;
+
+ if (start_col >= MAXCOL || end_col >= MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column index is too high");
+ return rv;
+ }
+
+ if (start_col > end_col) {
+ api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col");
+ return rv;
+ }
+
+ if ((size_t)start_col >= line_length) {
+ return rv;
+ }
+
+ rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col));
+ if (replace_nl) {
+ strchrsub(rv.data, '\n', '\0');
+ }
+
+ return rv;
+}
void api_free_string(String value)
{
@@ -1384,6 +1429,11 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
+ if (!uc_validate_name(name.data)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ goto err;
+ }
+
if (mb_islower(name.data[0])) {
api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
goto err;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index ada041bab2..4dc599564f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -31,6 +31,7 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/lua/executor.h"
@@ -123,6 +124,10 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// Set a highlight group.
///
+/// Note: unlike the `:highlight` command which can update a highlight group,
+/// this function completely replaces the definition. For example:
+/// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'.
+///
/// @param ns_id number of namespace for this highlight. Use value 0
/// to set a highlight group in the global (`:highlight`)
/// namespace.
@@ -496,8 +501,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- do_in_runtimepath((char_u *)(name.size ? name.data : ""),
- flags, find_runtime_cb, &rv);
+ TRY_WRAP({
+ try_start();
+ do_in_runtimepath((char_u *)(name.size ? name.data : ""),
+ flags, find_runtime_cb, &rv);
+ try_end(err);
+ });
return rv;
}
@@ -545,20 +554,19 @@ void nvim_set_current_dir(String dir, Error *err)
return;
}
- char string[MAXPATHL];
+ char_u string[MAXPATHL];
memcpy(string, dir.data, dir.size);
string[dir.size] = NUL;
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (!changedir_func(string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to change directory");
}
return;
}
- post_chdir(kCdScopeGlobal, true);
try_end(err);
}
@@ -601,7 +609,19 @@ void nvim_del_current_line(Error *err)
Object nvim_get_var(String name, Error *err)
FUNC_API_SINCE(1)
{
- return dict_get_value(&globvardict, name, err);
+ dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ if (di == NULL) { // try to autoload script
+ if (!script_autoload(name.data, name.size, false) || aborting()) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ }
+ if (di == NULL) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ return vim_to_object(&di->di_tv);
}
/// Sets a global (g:) variable.
@@ -1572,7 +1592,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
/// @param rhs Right-hand-side |{rhs}| of the mapping.
/// @param opts Optional parameters map. Accepts all |:map-arguments|
/// as keys excluding |<buffer>| but including |noremap| and "desc".
-/// |desc| can be used to give a description to keymap.
+/// "desc" can be used to give a description to keymap.
/// When called from Lua, also accepts a "callback" key that takes
/// a Lua function to call when the mapping is executed.
/// Values are Booleans. Unknown key is an error.
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 9fd4de4bb2..fc7823a070 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -395,7 +395,7 @@ void nvim_win_hide(Window window, Error *err)
TryState tstate;
try_enter(&tstate);
if (tabpage == curtab) {
- win_close(win, false);
+ win_close(win, false, false);
} else {
win_close_othertab(win, false, tabpage);
}
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 8fe623fc96..518d0b52b2 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -40,6 +40,7 @@ return {
'DiagnosticChanged', -- diagnostics in a buffer were modified
'DiffUpdated', -- diffs have been updated
'DirChanged', -- directory changed
+ 'DirChangedPre', -- directory is going to change
'EncodingChanged', -- after changing the 'encoding' option
'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
@@ -132,18 +133,14 @@ return {
nvim_specific = {
BufModifiedSet=true,
DiagnosticChanged=true,
- DirChanged=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true,
- TabClosed=true,
- TabNew=true,
TabNewEntered=true,
TermClose=true,
TermOpen=true,
UIEnter=true,
UILeave=true,
- WinClosed=true,
WinScrolled=true,
},
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 2e7f9c5136..9117dde089 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -285,7 +285,7 @@ void aubuflocal_remove(buf_T *buf)
}
// Add an autocmd group name.
-// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
+// Return its ID.
static int au_new_group(char_u *name)
{
int i = au_find_group(name);
@@ -379,10 +379,7 @@ void do_augroup(char_u *arg, int del_group)
} else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
current_augroup = AUGROUP_DEFAULT;
} else if (*arg) { // ":aug xxx": switch to group xxx
- int i = au_new_group(arg);
- if (i != AUGROUP_ERROR) {
- current_augroup = i;
- }
+ current_augroup = au_new_group(arg);
} else { // ":aug": list the group names
msg_start();
for (int i = 0; i < augroups.ga_len; i++) {
@@ -1520,12 +1517,13 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER
|| event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED
|| event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
- || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE
- || event == EVENT_FUNCUNDEFINED || event == EVENT_MODECHANGED
- || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST
- || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY
- || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX
- || event == EVENT_SIGNAL || event == EVENT_TABCLOSED
+ || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE
+ || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED
+ || event == EVENT_MODECHANGED || event == EVENT_OPTIONSET
+ || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE
+ || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING
+ || event == EVENT_SYNTAX || event == EVENT_SIGNAL
+ || event == EVENT_TABCLOSED || event == EVENT_USER
|| event == EVENT_WINCLOSED) {
fname = vim_strsave(fname);
} else {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index bb8483f644..dd40623af2 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -846,7 +846,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
enter_cleanup(&cs);
// Quitting means closing the split window, nothing else.
- win_close(curwin, true);
+ win_close(curwin, true, false);
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -1237,7 +1237,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
- if (win_close(curwin, false) == FAIL) {
+ if (win_close(curwin, false, false) == FAIL) {
break;
}
}
@@ -1441,7 +1441,7 @@ void set_curbuf(buf_T *buf, int action)
set_bufref(&prevbufref, prevbuf);
set_bufref(&newbufref, buf);
- // Autocommands may delete the curren buffer and/or the buffer we want to go
+ // Autocommands may delete the current buffer and/or the buffer we want to go
// to. In those cases don't close the buffer.
if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf)
|| (bufref_valid(&prevbufref) && bufref_valid(&newbufref)
@@ -1454,6 +1454,7 @@ void set_curbuf(buf_T *buf, int action)
}
if (bufref_valid(&prevbufref) && !aborting()) {
win_T *previouswin = curwin;
+
// Do not sync when in Insert mode and the buffer is open in
// another window, might be a timer doing something in another
// window.
@@ -1497,6 +1498,11 @@ void set_curbuf(buf_T *buf, int action)
*/
void enter_buffer(buf_T *buf)
{
+ // Get the buffer in the current window.
+ curwin->w_buffer = buf;
+ curbuf = buf;
+ curbuf->b_nwindows++;
+
// Copy buffer and window local option values. Not for a help buffer.
buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
if (!buf->b_help) {
@@ -1507,11 +1513,6 @@ void enter_buffer(buf_T *buf)
}
foldUpdateAll(curwin); // update folds (later).
- // Get the buffer in the current window.
- curwin->w_buffer = buf;
- curbuf = buf;
- curbuf->b_nwindows++;
-
if (curwin->w_p_diff) {
diff_buf_add(curbuf);
}
@@ -3327,7 +3328,7 @@ void maketitle(void)
len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
- len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
+ len += mb_tail_off(buf_p, buf_p + len) + 1;
buf_p += len;
}
STRCPY(icon_str, buf_p);
@@ -3438,8 +3439,12 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (stl_items == NULL) {
stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
- stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
- stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len);
+
+ // Allocate one more, because the last element is used to indicate the
+ // end of the list.
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
+ stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
+
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
}
@@ -3514,8 +3519,8 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len);
stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len);
- stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len);
- stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len);
+ stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1));
+ stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1));
stl_separator_locations =
xrealloc(stl_separator_locations, sizeof(int) * new_len);
@@ -4822,7 +4827,7 @@ void do_arg_all(int count, int forceit, int keep_tabs)
&& (first_tabpage->tp_next == NULL || !had_tab)) {
use_firstwin = true;
} else {
- win_close(wp, !buf_hide(buf) && !bufIsChanged(buf));
+ win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
@@ -5013,7 +5018,7 @@ void ex_buffer_all(exarg_T *eap)
&& !ONE_WINDOW
&& !(wp->w_closing
|| wp->w_buffer->b_locked > 0)) {
- win_close(wp, false);
+ win_close(wp, false, false);
wpnext = firstwin; // just in case an autocommand does
// something strange with windows
tpnext = first_tabpage; // start all over...
@@ -5094,7 +5099,7 @@ void ex_buffer_all(exarg_T *eap)
enter_cleanup(&cs);
// User selected Quit at ATTENTION prompt; close this window.
- win_close(curwin, true);
+ win_close(curwin, true, false);
open_wins--;
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -5136,7 +5141,7 @@ void ex_buffer_all(exarg_T *eap)
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
} else if (r) {
- win_close(wp, !buf_hide(wp->w_buffer));
+ win_close(wp, !buf_hide(wp->w_buffer), false);
open_wins--;
wp = lastwin;
} else {
@@ -5455,30 +5460,43 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp)
return false;
}
-int buf_signcols(buf_T *buf)
+static int buf_signcols_inner(buf_T *buf, int maximum)
{
- if (!buf->b_signcols_valid) {
- sign_entry_T *sign; // a sign in the sign list
- int signcols = 0;
- int linesum = 0;
- linenr_T curline = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > curline) {
- if (linesum > signcols) {
- signcols = linesum;
+ sign_entry_T *sign; // a sign in the sign list
+ int signcols = 0;
+ int linesum = 0;
+ linenr_T curline = 0;
+
+ FOR_ALL_SIGNS_IN_BUF(buf, sign) {
+ if (sign->se_lnum > curline) {
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
}
- curline = sign->se_lnum;
- linesum = 0;
- }
- if (sign->se_has_text_or_icon) {
- linesum++;
}
+ curline = sign->se_lnum;
+ linesum = 0;
}
- if (linesum > signcols) {
- signcols = linesum;
+ if (sign->se_has_text_or_icon) {
+ linesum++;
}
+ }
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
+ }
+ }
+
+ return signcols;
+}
+
+int buf_signcols(buf_T *buf, int maximum)
+{
+ if (!buf->b_signcols_valid) {
+ int signcols = buf_signcols_inner(buf, maximum);
// Check if we need to redraw
if (signcols != buf->b_signcols) {
buf->b_signcols = signcols;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 7b17c5b506..7ae5df164f 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -587,7 +587,9 @@ struct file_buffer {
// where invoked
long b_mtime; // last change time of original file
+ long b_mtime_ns; // nanoseconds of last change time
long b_mtime_read; // last change time when reading
+ long b_mtime_read_ns; // nanoseconds of last read time
uint64_t b_orig_size; // size of original file in bytes
int b_orig_mode; // mode of original file
time_t b_last_used; // time when the buffer was last used; used
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 54c4ba5319..736867b6d3 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -223,19 +223,20 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
// values for the cursor.
// Update the folds for this window. Can't postpone this, because
// a following operator might work on the whole fold: ">>dd".
- foldUpdate(wp, lnum, lnume + xtra - 1);
+ linenr_T last = lnume + xtra - 1; // last line after the change
+ foldUpdate(wp, lnum, last);
// The change may cause lines above or below the change to become
// included in a fold. Set lnum/lnume to the first/last line that
// might be displayed differently.
// Set w_cline_folded here as an efficient way to update it when
- // inserting lines just above a closed fold. */
+ // inserting lines just above a closed fold.
bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
if (wp->w_cursor.lnum == lnum) {
wp->w_cline_folded = folded;
}
- folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
- if (wp->w_cursor.lnum == lnume) {
+ folded = hasFoldingWin(wp, last, NULL, &last, false, NULL);
+ if (wp->w_cursor.lnum == last) {
wp->w_cline_folded = folded;
}
@@ -1201,10 +1202,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// Find out if the current line starts with a comment leader.
// This may then be inserted in front of the new line.
end_comment_pending = NUL;
- if (flags & OPENLINE_DO_COM && dir == FORWARD) {
- // Check for a line comment after code.
+ if (flags & OPENLINE_DO_COM) {
lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
- if (lead_len == 0 && do_cindent) {
+ if (lead_len == 0 && do_cindent && dir == FORWARD) {
+ // Check for a line comment after code.
comment_start = check_linecomment(saved_line);
if (comment_start != MAXCOL) {
lead_len = get_leader_len(saved_line + comment_start,
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index cd5134fe5f..d79c0acc4a 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -613,7 +613,6 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_
} else {
if (chan->term) {
terminal_receive(chan->term, ptr, count);
- terminal_flush_output(chan->term);
}
rbuffer_consumed(buf, count);
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 583a040ed1..f4882e57e1 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1439,7 +1439,7 @@ bool vim_isblankline(char_u *lbuf)
/// @param unptr Returns the unsigned result.
/// @param maxlen Max length of string to check.
/// @param strict If true, fail if the number has unexpected trailing
-/// alpha-numeric chars: *len is set to 0 and nothing else is
+/// alphanumeric chars: *len is set to 0 and nothing else is
/// returned.
void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what,
varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen,
@@ -1585,7 +1585,7 @@ vim_str2nr_hex:
#undef PARSE_NUMBER
vim_str2nr_proceed:
- // Check for an alpha-numeric character immediately following, that is
+ // Check for an alphanumeric character immediately following, that is
// most likely a typo.
if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) {
return;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 926c385892..07f0799bc4 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -766,6 +766,15 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
return ret;
}
+/// @return whether a typval is a valid expression to pass to eval_expr_typval()
+/// or eval_expr_to_bool(). An empty string returns false;
+bool eval_expr_valid_arg(const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST
+{
+ return tv->v_type != VAR_UNKNOWN
+ && (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
+}
+
int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
@@ -8183,7 +8192,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a
/// character index. Works only for loaded buffers. Returns -1 on failure.
-/// The index of the first character is one.
+/// The index of the first byte and the first character is zero.
int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx)
{
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
@@ -8197,15 +8206,29 @@ int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx)
char_u *str = ml_get_buf(buf, lnum, false);
if (*str == NUL) {
- return 1;
+ return 0;
+ }
+
+ // count the number of characters
+ char_u *t = str;
+ int count;
+ for (count = 0; *t != NUL && t <= str + byteidx; count++) {
+ t += utfc_ptr2len(t);
+ }
+
+ // In insert mode, when the cursor is at the end of a non-empty line,
+ // byteidx points to the NUL character immediately past the end of the
+ // string. In this case, add one to the character count.
+ if (*t == NUL && byteidx != 0 && t == str + byteidx) {
+ count++;
}
- return mb_charlen_len(str, byteidx + 1);
+ return count - 1;
}
/// Convert the specified character index of line 'lnum' in buffer 'buf' to a
-/// byte index. Works only for loaded buffers. Returns -1 on failure. The index
-/// of the first byte and the first character is one.
+/// byte index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first byte and the first character is zero.
int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx)
{
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
@@ -8224,7 +8247,7 @@ int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx)
t += utfc_ptr2len(t);
}
- return t - str + 1;
+ return t - str;
}
/// Translate a VimL object into a position
@@ -8306,7 +8329,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
if (name[0] == '.') { // Cursor.
pos = curwin->w_cursor;
if (charcol) {
- pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1;
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
}
return &pos;
}
@@ -8317,7 +8340,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos = curwin->w_cursor;
}
if (charcol) {
- pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1;
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
}
return &pos;
}
@@ -8327,7 +8350,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
return NULL;
}
if (charcol) {
- pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1;
+ pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col);
}
return pp;
}
@@ -8414,7 +8437,7 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
return FAIL;
}
- n = buf_charidx_to_byteidx(buf, posp->lnum, n);
+ n = buf_charidx_to_byteidx(buf, posp->lnum, n) + 1;
}
posp->col = n;
@@ -10700,7 +10723,7 @@ repeat:
pbuf = NULL;
// Need full path first (use expand_env() to remove a "~/")
if (!has_fullname && !has_homerelative) {
- if ((c == '.' || c == '~') && **fnamep == '~') {
+ if (**fnamep == '~') {
p = pbuf = expand_env_save(*fnamep);
} else {
p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE);
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index eedc8ac45d..05e91a658f 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -249,6 +249,8 @@ return {
matcharg={args=1, base=1},
matchdelete={args={1, 2}, base=1},
matchend={args={2, 4}, base=1},
+ matchfuzzy={args={2, 3}, base=1},
+ matchfuzzypos={args={2, 3}, base=1},
matchlist={args={2, 4}, base=1},
matchstr={args={2, 4}, base=1},
matchstrpos={args={2,4}, base=1},
@@ -280,6 +282,7 @@ return {
range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
+ reduce={args={2, 3}, base=1},
reg_executing={},
reg_recording={},
reg_recorded={},
@@ -304,12 +307,12 @@ return {
screenpos={args=3, base=1},
screenrow={},
screenstring={args=2, base=1},
- search={args={1, 4}, base=1},
+ search={args={1, 5}, base=1},
searchcount={args={0, 1}, base=1},
searchdecl={args={1, 3}, base=1},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
- searchpos={args={1, 4}, base=1},
+ searchpos={args={1, 5}, base=1},
serverlist={},
serverstart={args={0, 1}},
serverstop={args=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 138745094c..3763390c22 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -98,9 +98,9 @@ PRAGMA_DIAG_POP
#endif
-static char *e_listarg = N_("E686: Argument of %s must be a List");
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");
/// Dummy va_list for passing to vim_snprintf
///
@@ -1588,7 +1588,7 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
line = tv_get_lnum(argvars);
col = (long)tv_get_number_chk(&argvars[1], NULL);
if (charcol) {
- col = buf_charidx_to_byteidx(curbuf, line, col);
+ col = buf_charidx_to_byteidx(curbuf, line, col) + 1;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
coladd = (long)tv_get_number_chk(&argvars[2], NULL);
@@ -3225,7 +3225,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
set_vim_var_nr(VV_MOUSE_COL, 0);
rettv->vval.v_number = n;
- if (IS_SPECIAL(n) || mod_mask != 0) {
+ if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) {
char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
int i = 0;
@@ -3327,7 +3327,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool
}
if (fp != NULL && charcol) {
pos = *fp;
- pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col) - 1;
+ pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col);
fp = &pos;
}
} else {
@@ -4539,6 +4539,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"mouse",
"multi_byte",
"multi_lang",
+ "nanotime",
"num64",
"packages",
"path_extra",
@@ -8055,6 +8056,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "reduce(list, { accumlator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_listblobreq));
+ return;
+ }
+
+ const char_u *func_name;
+ partial_T *partial = NULL;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ partial = argvars[1].vval.v_partial;
+ func_name = partial_name(partial);
+ } else {
+ func_name = (const char_u *)tv_get_string(&argvars[1]);
+ }
+ if (*func_name == NUL) {
+ return; // type error or empty name
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+
+ typval_T initial;
+ typval_T argv[3];
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ const listitem_T *li;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
+ } else {
+ initial = argvars[2];
+ li = tv_list_first(l);
+ }
+
+ tv_copy(&initial, rettv);
+
+ if (l != NULL) {
+ const VarLockStatus prev_locked = tv_list_locked(l);
+ const int called_emsg_start = called_emsg;
+
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
+ const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+ tv_list_set_lock(l, prev_locked);
+ }
+ } else {
+ const blob_T *const b = argvars[0].vval.v_blob;
+ int i;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
+ return;
+ }
+ initial.v_type = VAR_NUMBER;
+ initial.vval.v_number = tv_blob_get(b, 0);
+ i = 1;
+ } else if (argvars[2].v_type != VAR_NUMBER) {
+ emsg(_(e_number_exp));
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ argv[0] = *rettv;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = tv_blob_get(b, i);
+ if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
+ return;
+ }
+ }
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
@@ -8141,6 +8238,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
int options = SEARCH_KEEP;
int subpatnum;
searchit_arg_T sia;
+ bool use_skip = false;
const char *const pat = tv_get_string(&argvars[0]);
dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
@@ -8158,7 +8256,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
options |= SEARCH_COL;
}
- // Optional arguments: line number to stop searching and timeout.
+ // Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
@@ -8169,6 +8267,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
if (time_limit < 0) {
goto theend;
}
+ use_skip = eval_expr_valid_arg(&argvars[4]);
}
}
@@ -8188,11 +8287,46 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
pos = save_cursor = curwin->w_cursor;
+ pos_T firstpos = { 0 };
memset(&sia, 0, sizeof(sia));
sia.sa_stop_lnum = (linenr_T)lnum_stop;
sia.sa_tm = &tm;
- subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, &sia);
+
+ // Repeat until {skip} returns false.
+ for (;;) {
+ subpatnum
+ = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia);
+ // finding the first match again means there is no match where {skip}
+ // evaluates to zero.
+ if (firstpos.lnum != 0 && equalpos(pos, firstpos)) {
+ subpatnum = FAIL;
+ }
+
+ if (subpatnum == FAIL || !use_skip) {
+ // didn't find it or no skip argument
+ break;
+ }
+ firstpos = pos;
+
+ // If the skip expression matches, ignore this match.
+ {
+ const pos_T save_pos = curwin->w_cursor;
+
+ curwin->w_cursor = pos;
+ bool err = false;
+ const bool do_skip = eval_expr_to_bool(&argvars[4], &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ subpatnum = FAIL;
+ break;
+ }
+ if (!do_skip) {
+ break;
+ }
+ }
+ }
+
if (subpatnum != FAIL) {
if (flags & SP_SUBPAT) {
retval = subpatnum;
@@ -8652,13 +8786,9 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
|| argvars[4].v_type == VAR_UNKNOWN) {
skip = NULL;
} else {
+ // Type is checked later.
skip = &argvars[4];
- if (skip->v_type != VAR_FUNC
- && skip->v_type != VAR_PARTIAL
- && skip->v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[4]));
- goto theend; // Type error.
- }
+
if (argvars[5].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[5], NULL);
if (lnum_stop < 0) {
@@ -8769,10 +8899,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
}
if (skip != NULL) {
- // Empty string means to not use the skip expression.
- if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
- use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
- }
+ use_skip = eval_expr_valid_arg(skip);
}
save_cursor = curwin->w_cursor;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 3b3d4e50cc..49bf9972b1 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -814,7 +814,11 @@ void ex_retab(exarg_T *eap)
// len is actual number of white characters used
len = num_spaces + num_tabs;
old_len = (long)STRLEN(ptr);
- long new_len = old_len - col + start_col + len + 1;
+ const long new_len = old_len - col + start_col + len + 1;
+ if (new_len <= 0 || new_len >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
new_line = xmalloc(new_len);
if (start_col > 0) {
@@ -847,6 +851,10 @@ void ex_retab(exarg_T *eap)
break;
}
vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol);
+ if (vcol >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
col += utfc_ptr2len(ptr + col);
}
if (new_line == NULL) { // out of memory
@@ -5856,7 +5864,7 @@ void ex_helpclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (bt_help(win->w_buffer)) {
- win_close(win, false);
+ win_close(win, false, eap->forceit);
return;
}
}
@@ -6141,12 +6149,14 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags)
p++;
// Find the flags
- while (*p == 'g' || *p == 'j') {
+ while (*p == 'g' || *p == 'j' || *p == 'f') {
if (flags != NULL) {
if (*p == 'g') {
*flags |= VGR_GLOBAL;
- } else {
+ } else if (*p == 'j') {
*flags |= VGR_NOJUMP;
+ } else {
+ *flags |= VGR_FUZZY;
}
}
p++;
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index e5cec0e060..090422088a 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -12,6 +12,7 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/globals.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
@@ -2190,9 +2191,11 @@ void ex_scriptnames(exarg_T *eap)
if (SCRIPT_ITEM(i).sn_name != NULL) {
home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
- msg_putchar('\n');
- msg_outtrans(IObuff);
- line_breakcheck();
+ if (!message_filtered(IObuff)) {
+ msg_putchar('\n');
+ msg_outtrans(IObuff);
+ line_breakcheck();
+ }
}
}
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index d884838136..48749afcb3 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4141,7 +4141,11 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in
if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1'
n = 1;
} else {
- n = getdigits(&cmd, true, 0);
+ n = getdigits(&cmd, false, MAXLNUM);
+ if (n == MAXLNUM) {
+ emsg(_(e_line_number_out_of_range));
+ goto error;
+ }
}
if (addr_type == ADDR_TABS_RELATIVE) {
@@ -4160,6 +4164,10 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in
if (i == '-') {
lnum -= n;
} else {
+ if (n >= LONG_MAX - lnum) {
+ emsg(_(e_line_number_out_of_range));
+ goto error;
+ }
lnum += n;
}
}
@@ -5164,6 +5172,24 @@ char_u *get_command_name(expand_T *xp, int idx)
return cmdnames[idx].cmd_name;
}
+/// Check for a valid user command name
+///
+/// If the given {name} is valid, then a pointer to the end of the valid name is returned.
+/// Otherwise, returns NULL.
+char *uc_validate_name(char *name)
+{
+ if (ASCII_ISALPHA(*name)) {
+ while (ASCII_ISALNUM(*name)) {
+ name++;
+ }
+ }
+ if (!ends_excmd(*name) && !ascii_iswhite(*name)) {
+ return NULL;
+ }
+
+ return name;
+}
+
int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, int flags,
int compl, char_u *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type,
LuaRef luaref, bool force)
@@ -5679,23 +5705,18 @@ static void ex_command(exarg_T *eap)
// Get the name (if any) and skip to the following argument.
name = p;
- if (ASCII_ISALPHA(*p)) {
- while (ASCII_ISALNUM(*p)) {
- p++;
- }
- }
- if (!ends_excmd(*p) && !ascii_iswhite(*p)) {
+ end = (char_u *)uc_validate_name((char *)name);
+ if (!end) {
emsg(_("E182: Invalid command name"));
return;
}
- end = p;
- name_len = (int)(end - name);
+ name_len = (size_t)(end - name);
// If there is nothing after the name, and no attributes were specified,
// we are listing commands
p = skipwhite(end);
if (!has_attr && ends_excmd(*p)) {
- uc_list(name, end - name);
+ uc_list(name, name_len);
} else if (!ASCII_ISUPPER(*name)) {
emsg(_("E183: User defined commands must start with an uppercase letter"));
} else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) {
@@ -5703,7 +5724,7 @@ static void ex_command(exarg_T *eap)
} else if (compl > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_nargs));
} else {
- uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, LUA_NOREF,
+ uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF,
addr_type_arg, LUA_NOREF, eap->forceit);
}
}
@@ -6619,7 +6640,7 @@ static void ex_quit(exarg_T *eap)
}
not_exiting();
// close window; may free buffer
- win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
+ win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit);
}
}
@@ -6734,7 +6755,7 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
// free buffer when not hiding it or when it's a scratch buffer
if (tp == NULL) {
- win_close(win, !need_hide && !buf_hide(buf));
+ win_close(win, !need_hide && !buf_hide(buf), forceit);
} else {
win_close_othertab(win, !need_hide && !buf_hide(buf), tp);
}
@@ -6898,7 +6919,7 @@ static void ex_hide(exarg_T *eap)
// ":hide" or ":hide | cmd": hide current window
if (!eap->skip) {
if (eap->addr_count == 0) {
- win_close(curwin, false); // don't free buffer
+ win_close(curwin, false, eap->forceit); // don't free buffer
} else {
int winnr = 0;
win_T *win = NULL;
@@ -6913,7 +6934,7 @@ static void ex_hide(exarg_T *eap)
if (win == NULL) {
win = lastwin;
}
- win_close(win, false);
+ win_close(win, false, eap->forceit);
}
}
}
@@ -6971,7 +6992,7 @@ static void ex_exit(exarg_T *eap)
}
not_exiting();
// Quit current window, may free the buffer.
- win_close(curwin, !buf_hide(curwin->w_buffer));
+ win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit);
}
}
@@ -7485,7 +7506,7 @@ static void ex_edit(exarg_T *eap)
do_exedit(eap, NULL);
}
-/// ":edit <file>" command and alikes.
+/// ":edit <file>" command and alike.
///
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
@@ -7572,7 +7593,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
// Reset the error/interrupt/exception state here so that
// aborting() returns FALSE when closing a window.
enter_cleanup(&cs);
- win_close(curwin, !need_hide && !buf_hide(curbuf));
+ win_close(curwin, !need_hide && !buf_hide(curbuf), false);
// Restore the error/interrupt/exception state if not
// discarded by a new aborting error, interrupt, or
@@ -7784,7 +7805,7 @@ static char_u *get_prevdir(CdScope scope)
/// Deal with the side effects of changing the current directory.
///
/// @param scope Scope of the function call (global, tab or window).
-void post_chdir(CdScope scope, bool trigger_dirchanged)
+static void post_chdir(CdScope scope, bool trigger_dirchanged)
{
// Always overwrite the window-local CWD.
XFREE_CLEAR(curwin->w_localdir);
@@ -7825,7 +7846,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
shorten_fnames(true);
if (trigger_dirchanged) {
- do_autocmd_dirchanged(cwd, scope, kCdCauseManual);
+ do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false);
}
}
@@ -7869,10 +7890,13 @@ bool changedir_func(char_u *new_dir, CdScope scope)
}
bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
- if (dir_differs && vim_chdir(new_dir) != 0) {
- emsg(_(e_failed));
- xfree(pdir);
- return false;
+ if (dir_differs) {
+ do_autocmd_dirchanged((char *)new_dir, scope, kCdCauseManual, true);
+ if (vim_chdir(new_dir) != 0) {
+ emsg(_(e_failed));
+ xfree(pdir);
+ return false;
+ }
}
char_u **pp;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index f81f49a174..ed4475eb1a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -6563,7 +6563,7 @@ static int open_cmdwin(void)
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
if (win_valid(wp) && wp != curwin) {
- win_close(wp, true);
+ win_close(wp, true, false);
}
// win_close() may have already wiped the buffer when 'bh' is
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index 25dbf680de..d0f7a91d6c 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1600,11 +1600,13 @@ theend:
return file_name;
}
-void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
+void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre)
{
static bool recursive = false;
- if (recursive || !has_event(EVENT_DIRCHANGED)) {
+ event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED;
+
+ if (recursive || !has_event(event)) {
// No autocommand was defined or we changed
// the directory from this autocommand.
return;
@@ -1638,8 +1640,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
new_dir = new_dir_buf;
#endif
+ if (pre) {
+ tv_dict_add_str(dict, S_LEN("directory"), new_dir);
+ } else {
+ tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
+ }
tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
- tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict);
@@ -1655,8 +1661,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
abort();
}
- apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
- curbuf);
+ apply_autocmds(event, (char_u *)buf, (char_u *)new_dir, false, curbuf);
restore_v_event(dict, &save_v_event);
@@ -1682,12 +1687,16 @@ int vim_chdirfile(char_u *fname, CdCause cause)
return OK;
}
+ if (cause != kCdCauseOther) {
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true);
+ }
+
if (os_chdir(dir) != 0) {
return FAIL;
}
if (cause != kCdCauseOther) {
- do_autocmd_dirchanged(dir, kCdScopeWindow, cause);
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false);
}
return OK;
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f28ee1bfcb..f5824d83be 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -405,6 +405,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
if (os_fileinfo((char *)fname, &file_info)) {
buf_store_file_info(curbuf, &file_info);
curbuf->b_mtime_read = curbuf->b_mtime;
+ curbuf->b_mtime_read_ns = curbuf->b_mtime_ns;
#ifdef UNIX
/*
* Use the protection bits of the original file for the swap file.
@@ -421,7 +422,9 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
#endif
} else {
curbuf->b_mtime = 0;
+ curbuf->b_mtime_ns = 0;
curbuf->b_mtime_read = 0;
+ curbuf->b_mtime_read_ns = 0;
curbuf->b_orig_size = 0;
curbuf->b_orig_mode = 0;
}
@@ -2010,10 +2013,8 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp)
return lnum;
}
-/*
- * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
- * equal to the buffer "buf". Used for calling readfile().
- */
+/// Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary' to be
+/// equal to the buffer "buf". Used for calling readfile().
void prep_exarg(exarg_T *eap, const buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -3318,7 +3319,7 @@ restore_backup:
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
- && (lnum == buf->b_no_eol_lnum
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
lnum++; // written the line, count it
no_eol = true;
@@ -3695,11 +3696,12 @@ nofail:
msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
attr | MSG_HIST);
- /* Update the timestamp to avoid an "overwrite changed file"
- * prompt when writing again. */
+ // Update the timestamp to avoid an "overwrite changed file"
+ // prompt when writing again.
if (os_fileinfo((char *)fname, &file_info_old)) {
buf_store_file_info(buf, &file_info_old);
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -3893,8 +3895,7 @@ static void msg_add_eol(void)
static int check_mtime(buf_T *buf, FileInfo *file_info)
{
if (buf->b_mtime_read != 0
- && time_differs(file_info->stat.st_mtim.tv_sec,
- buf->b_mtime_read)) {
+ && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
msg_scroll = true; // Don't overwrite messages here.
msg_silent = 0; // Must give this prompt.
// Don't use emsg() here, don't want to flush the buffers.
@@ -3908,19 +3909,17 @@ static int check_mtime(buf_T *buf, FileInfo *file_info)
return OK;
}
-/// Return true if the times differ
-///
-/// @param t1 first time
-/// @param t2 second time
-static bool time_differs(long t1, long t2) FUNC_ATTR_CONST
+static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST
{
+ return (long)file_info->stat.st_mtim.tv_nsec != mtime_ns
#if defined(__linux__) || defined(MSWIN)
- // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
- // the seconds. Since the roundoff is done when flushing the inode, the
- // time may change unexpectedly by one second!!!
- return t1 - t2 > 1 || t2 - t1 > 1;
+ // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
+ // the seconds. Since the roundoff is done when flushing the inode, the
+ // time may change unexpectedly by one second!!!
+ || (long)file_info->stat.st_mtim.tv_sec - mtime > 1
+ || mtime - (long)file_info->stat.st_mtim.tv_sec > 1;
#else
- return t1 != t2;
+ || (long)file_info->stat.st_mtim.tv_sec != mtime;
#endif
}
@@ -4900,13 +4899,11 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
return retval;
}
-/*
- * Check if buffer "buf" has been changed.
- * Also check if the file for a new buffer unexpectedly appeared.
- * return 1 if a changed buffer was found.
- * return 2 if a message has been displayed.
- * return 0 otherwise.
- */
+/// Check if buffer "buf" has been changed.
+/// Also check if the file for a new buffer unexpectedly appeared.
+/// return 1 if a changed buffer was found.
+/// return 2 if a message has been displayed.
+/// return 0 otherwise.
int buf_check_timestamp(buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -4915,7 +4912,11 @@ int buf_check_timestamp(buf_T *buf)
char *mesg = NULL;
char *mesg2 = "";
bool helpmesg = false;
- bool reload = false;
+ enum {
+ RELOAD_NONE,
+ RELOAD_NORMAL,
+ RELOAD_DETECT
+ } reload = RELOAD_NONE;
bool can_reload = false;
uint64_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
@@ -4943,7 +4944,7 @@ int buf_check_timestamp(buf_T *buf)
if (!(buf->b_flags & BF_NOTEDITED)
&& buf->b_mtime != 0
&& (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info))
- || time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime)
+ || time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns)
|| (int)file_info.stat.st_mode != buf->b_orig_mode)) {
const long prev_b_mtime = buf->b_mtime;
@@ -4968,7 +4969,7 @@ int buf_check_timestamp(buf_T *buf)
// If 'autoread' is set, the buffer has no changes and the file still
// exists, reload the buffer. Use the buffer-local option value if it
// was set, the global option value otherwise.
- reload = true;
+ reload = RELOAD_NORMAL;
} else {
if (!file_info_ok) {
reason = "deleted";
@@ -4999,7 +5000,9 @@ int buf_check_timestamp(buf_T *buf)
}
s = get_vim_var_str(VV_FCS_CHOICE);
if (STRCMP(s, "reload") == 0 && *reason != 'd') {
- reload = true;
+ reload = RELOAD_NORMAL;
+ } else if (STRCMP(s, "edit") == 0) {
+ reload = RELOAD_DETECT;
} else if (STRCMP(s, "ask") == 0) {
n = false;
} else {
@@ -5034,6 +5037,7 @@ int buf_check_timestamp(buf_T *buf)
// Only timestamp changed, store it to avoid a warning
// in check_mtime() later.
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -5062,9 +5066,15 @@ int buf_check_timestamp(buf_T *buf)
xstrlcat(tbuf, "\n", tbuf_len - 1);
xstrlcat(tbuf, mesg2, tbuf_len - 1);
}
- if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
- (char_u *)_("&OK\n&Load File"), 1, NULL, true) == 2) {
- reload = true;
+ switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
+ (char_u *)_("&OK\n&Load File\nLoad File &and Options"),
+ 1, NULL, true)) {
+ case 2:
+ reload = RELOAD_NORMAL;
+ break;
+ case 3:
+ reload = RELOAD_DETECT;
+ break;
}
} else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) {
if (*mesg2 != NUL) {
@@ -5098,9 +5108,9 @@ int buf_check_timestamp(buf_T *buf)
xfree(tbuf);
}
- if (reload) {
+ if (reload != RELOAD_NONE) {
// Reload the buffer.
- buf_reload(buf, orig_mode);
+ buf_reload(buf, orig_mode, reload == RELOAD_DETECT);
if (buf->b_p_udf && buf->b_ffname != NULL) {
char_u hash[UNDO_HASH_SIZE];
@@ -5118,13 +5128,11 @@ int buf_check_timestamp(buf_T *buf)
return retval;
}
-/*
- * Reload a buffer that is already loaded.
- * Used when the file was changed outside of Vim.
- * "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
- * buf->b_orig_mode may have been reset already.
- */
-void buf_reload(buf_T *buf, int orig_mode)
+/// Reload a buffer that is already loaded.
+/// Used when the file was changed outside of Vim.
+/// "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
+/// buf->b_orig_mode may have been reset already.
+void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
{
exarg_T ea;
pos_T old_cursor;
@@ -5139,11 +5147,15 @@ void buf_reload(buf_T *buf, int orig_mode)
// set curwin/curbuf for "buf" and save some things
aucmd_prepbuf(&aco, buf);
- // We only want to read the text from the file, not reset the syntax
- // highlighting, clear marks, diff status, etc. Force the fileformat and
- // encoding to be the same.
+ // Unless reload_options is set, we only want to read the text from the
+ // file, not reset the syntax highlighting, clear marks, diff status, etc.
+ // Force the fileformat and encoding to be the same.
+ if (reload_options) {
+ memset(&ea, 0, sizeof(ea));
+ } else {
+ prep_exarg(&ea, buf);
+ }
- prep_exarg(&ea, buf);
old_cursor = curwin->w_cursor;
old_topline = curwin->w_topline;
@@ -5262,6 +5274,7 @@ void buf_store_file_info(buf_T *buf, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
buf->b_mtime = file_info->stat.st_mtim.tv_sec;
+ buf->b_mtime_ns = file_info->stat.st_mtim.tv_nsec;
buf->b_orig_size = os_fileinfo_size(file_info);
buf->b_orig_mode = (int)file_info->stat.st_mode;
}
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 5d8a8ddbfe..34cde9a7c4 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1004,20 +1004,9 @@ int ins_char_typebuf(int c, int modifier)
buf[len + 2] = (char_u)K_THIRD(c);
buf[len + 3] = NUL;
} else {
- char_u *p = buf + len;
- int char_len = utf_char2bytes(c, p);
- len += char_len;
- // If the character contains K_SPECIAL bytes they need escaping.
- for (int i = char_len; --i >= 0; p++) {
- if ((uint8_t)(*p) == K_SPECIAL) {
- memmove(p + 3, p + 1, (size_t)i);
- *p++ = K_SPECIAL;
- *p++ = KS_SPECIAL;
- *p = KE_FILLER;
- len += 2;
- }
- }
- *p = NUL;
+ char_u *end = add_char2buf(c, buf + len);
+ *end = NUL;
+ len = (int)(end - buf);
}
(void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
return len;
@@ -2466,7 +2455,7 @@ static int vgetorpeek(bool advance)
/// 1. a scriptfile
/// 2. the keyboard
///
-/// As much characters as we can get (up to 'maxlen') are put in "buf" and
+/// As many characters as we can get (up to 'maxlen') are put in "buf" and
/// NUL terminated (buffer length must be 'maxlen' + 1).
/// Minimum for "maxlen" is 3!!!!
///
@@ -2679,8 +2668,7 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
mapargs->orig_rhs_len = 0;
// stores <lua>ref_no<cr> in map_str
mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
- (char_u)KEY2TERMCAP0(K_LUA), KEY2TERMCAP1(K_LUA),
- rhs_lua);
+ (char_u)KS_EXTRA, KE_LUA, rhs_lua);
mapargs->rhs = vim_strsave((char_u *)tmp_buf);
}
@@ -3434,8 +3422,8 @@ static void showmap(mapblock_T *mp, bool local)
{
size_t len = 1;
- if (message_filtered(mp->m_keys)
- && mp->m_str != NULL && message_filtered(mp->m_str)) {
+ if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)
+ && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) {
return;
}
@@ -3484,7 +3472,7 @@ static void showmap(mapblock_T *mp, bool local)
char msg[100];
snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref);
msg_puts_attr(msg, HL_ATTR(HLF_8));
- } else if (mp->m_str == NULL) {
+ } else if (mp->m_str[0] == NUL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
// Remove escaping of K_SPECIAL, because "m_str" is in a format to be used
@@ -3584,8 +3572,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
mp = maphash[hash];
}
for (; mp; mp = mp->m_next) {
- if ((mp->m_mode & mode)
- && mp->m_str != NULL && strstr((char *)mp->m_str, rhs) != NULL) {
+ if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) {
return true;
}
}
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index be10e150e5..f24a4e7c7c 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -55,7 +55,7 @@ struct map_arguments {
char_u *orig_rhs; /// The original text of the {rhs}.
size_t orig_rhs_len;
- char *desc; /// map escription
+ char *desc; /// map description
};
typedef struct map_arguments MapArguments;
#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 041b60d838..5aa564623f 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -979,6 +979,7 @@ EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
@@ -999,6 +1000,10 @@ EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string req
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"));
+
+EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
+
EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 87c090e594..bbad27014d 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -815,31 +815,33 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL);
CHECK_FLAG(dict, mask, italic, , HL_ITALIC);
CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
CHECK_FLAG(dict, mask, global, , HL_GLOBAL);
if (HAS_KEY(dict->fg)) {
- fg = object_to_color(dict->fg, "fg", err);
+ fg = object_to_color(dict->fg, "fg", true, err);
} else if (HAS_KEY(dict->foreground)) {
- fg = object_to_color(dict->foreground, "foreground", err);
+ fg = object_to_color(dict->foreground, "foreground", true, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
if (HAS_KEY(dict->bg)) {
- bg = object_to_color(dict->bg, "bg", err);
+ bg = object_to_color(dict->bg, "bg", true, err);
} else if (HAS_KEY(dict->background)) {
- bg = object_to_color(dict->background, "background", err);
+ bg = object_to_color(dict->background, "background", true, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
if (HAS_KEY(dict->sp)) {
- sp = object_to_color(dict->sp, "sp", err);
+ sp = object_to_color(dict->sp, "sp", true, err);
} else if (HAS_KEY(dict->special)) {
- sp = object_to_color(dict->special, "special", err);
+ sp = object_to_color(dict->special, "special", true, err);
}
if (ERROR_SET(err)) {
return hlattrs;
@@ -871,21 +873,27 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL);
CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC);
CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE);
+ } else if (dict->cterm.type == kObjectTypeArray && dict->cterm.data.array.size == 0) {
+ // empty list from Lua API should clear all cterm attributes
+ // TODO(clason): handle via gen_api_dispatch
+ cterm_mask_provided = true;
} else if (HAS_KEY(dict->cterm)) {
api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
}
#undef CHECK_FLAG
if (HAS_KEY(dict->ctermfg)) {
- ctermfg = object_to_color(dict->ctermfg, "ctermfg", err);
+ ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
}
}
if (HAS_KEY(dict->ctermbg)) {
- ctermbg = object_to_color(dict->ctermbg, "ctermbg", err);
+ ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
}
@@ -900,28 +908,38 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
hlattrs.rgb_bg_color = bg;
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
- hlattrs.cterm_bg_color =
- ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1;
- hlattrs.cterm_fg_color =
- ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1;
+ hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1;
+ hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1;
hlattrs.cterm_ae_attr = cterm_mask;
} else {
hlattrs.cterm_ae_attr = cterm_mask;
- hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
- hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
+ hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1;
+ hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1;
}
return hlattrs;
}
-int object_to_color(Object val, char *key, Error *err)
+int object_to_color(Object val, char *key, bool rgb, Error *err)
{
if (val.type == kObjectTypeInteger) {
return (int)val.data.integer;
} else if (val.type == kObjectTypeString) {
String str = val.data.string;
// TODO(bfredl): be more fancy with "bg", "fg" etc
- return str.size ? name_to_color(str.data) : 0;
+ if (!str.size || STRICMP(str.data, "NONE") == 0) {
+ return -1;
+ }
+ int color;
+ if (rgb) {
+ color = name_to_color(str.data);
+ } else {
+ color = name_to_ctermcolor(str.data);
+ }
+ if (color < 0) {
+ api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data);
+ }
+ return color;
} else {
api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
return 0;
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index cbfb8364f6..029e7eb660 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1241,6 +1241,9 @@ void ex_luafile(exarg_T *const eap)
/// execute lua code from a file.
///
+/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile
+/// in case loadfile has been overridden in the users environment.
+///
/// @param path path of the file
///
/// @return true if everything ok, false if there was an error (echoed)
@@ -1249,11 +1252,30 @@ bool nlua_exec_file(const char *path)
{
lua_State *const lstate = global_lstate;
- if (luaL_loadfile(lstate, path)) {
+ lua_getglobal(lstate, "loadfile");
+ lua_pushstring(lstate, path);
+
+ if (nlua_pcall(lstate, 1, 2)) {
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
+ return false;
+ }
+
+ // loadstring() returns either:
+ // 1. nil, error
+ // 2. chunk, nil
+
+ if (lua_isnil(lstate, -2)) {
+ // 1
nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
+ assert(lua_isnil(lstate, -1));
+ lua_pop(lstate, 1);
return false;
}
+ // 2
+ assert(lua_isnil(lstate, -1));
+ lua_pop(lstate, 1);
+
if (nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s"));
return false;
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 18a579ed0f..55b23cf0c8 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -25,6 +25,7 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/stdlib.h"
@@ -408,6 +409,12 @@ int nlua_getvar(lua_State *lstate)
const char *name = luaL_checklstring(lstate, 3, &len);
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL && dict == &globvardict) { // try to autoload script
+ if (!script_autoload(name, len, false) || aborting()) {
+ return 0; // nil
+ }
+ di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ }
if (di == NULL) {
return 0; // nil
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 748f5098fd..8991ae7f00 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1560,7 +1560,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
@@ -1572,7 +1572,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
arg_idx++;
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
continue;
}
@@ -1619,7 +1619,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
did_emsg = FALSE; // avoid hit-enter prompt
getout(1);
}
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
if (arg_idx == GARGCOUNT - 1) {
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 7fa2562be0..f634c7dda8 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1317,6 +1317,12 @@ bool mb_isupper(int a)
return mb_tolower(a) != a;
}
+bool mb_isalpha(int a)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return mb_islower(a) || mb_isupper(a);
+}
+
static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2)
{
int c1, c2, cdiff;
@@ -1821,12 +1827,10 @@ void mb_copy_char(const char_u **const fp, char_u **const tp)
*fp += l;
}
-/*
- * Return the offset from "p" to the first byte of a character. When "p" is
- * at the start of a character 0 is returned, otherwise the offset to the next
- * character. Can start anywhere in a stream of bytes.
- */
-int mb_off_next(char_u *base, char_u *p)
+/// Return the offset from "p" to the first byte of a character. When "p" is
+/// at the start of a character 0 is returned, otherwise the offset to the next
+/// character. Can start anywhere in a stream of bytes.
+int mb_off_next(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1854,7 +1858,7 @@ int mb_off_next(char_u *base, char_u *p)
/// Return the offset from "p" to the last byte of the character it points
/// into. Can start anywhere in a stream of bytes.
/// Composing characters are not included.
-int mb_tail_off(char_u *base, char_u *p)
+int mb_tail_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1882,12 +1886,13 @@ int mb_tail_off(char_u *base, char_u *p)
/// Return the offset from "p" to the first byte of the character it points
/// into. Can start anywhere in a stream of bytes.
+/// Unlike utf_head_off() this doesn't include composing characters and returns a negative value.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
/// @return 0 if invalid sequence, else offset to previous codepoint
-int mb_head_off(char_u *base, char_u *p)
+int mb_head_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -2037,13 +2042,11 @@ char_u *mb_prevptr(char_u *line, char_u *p)
return p;
}
-/*
- * Return the character length of "str". Each multi-byte character (with
- * following composing characters) counts as one.
- */
-int mb_charlen(char_u *str)
+/// Return the character length of "str". Each multi-byte character (with
+/// following composing characters) counts as one.
+int mb_charlen(const char_u *str)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
if (p == NULL) {
@@ -2057,12 +2060,10 @@ int mb_charlen(char_u *str)
return count;
}
-/*
- * Like mb_charlen() but for a string with specified length.
- */
-int mb_charlen_len(char_u *str, int len)
+/// Like mb_charlen() but for a string with specified length.
+int mb_charlen_len(const char_u *str, int len)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
for (count = 0; *p != NUL && p < str + len; count++) {
@@ -2201,11 +2202,9 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET
return r;
}
-/*
- * Search for an encoding alias of "name".
- * Returns -1 when not found.
- */
-static int enc_alias_search(char_u *name)
+/// Search for an encoding alias of "name".
+/// Returns -1 when not found.
+static int enc_alias_search(const char_u *name)
{
int i;
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 9925971783..004ef36b36 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -704,11 +704,14 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino);
buf_store_file_info(buf, &file_info);
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
} else {
long_to_char(0L, b0p->b0_mtime);
long_to_char(0L, b0p->b0_ino);
buf->b_mtime = 0;
+ buf->b_mtime_ns = 0;
buf->b_mtime_read = 0;
+ buf->b_mtime_read_ns = 0;
buf->b_orig_size = 0;
buf->b_orig_mode = 0;
}
@@ -1720,6 +1723,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
FileInfo file_info;
if (!os_fileinfo((char *)buf->b_ffname, &file_info)
|| file_info.stat.st_mtim.tv_sec != buf->b_mtime_read
+ || file_info.stat.st_mtim.tv_nsec != buf->b_mtime_read_ns
|| os_fileinfo_size(&file_info) != buf->b_orig_size) {
ml_preserve(buf, false, do_fsync);
did_check_timestamps = false;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 93742ccbdb..b39450cdc6 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -3464,6 +3464,7 @@ void msg_advance(int col)
///
/// @param textfiel IObuff for inputdialog(), NULL otherwise
/// @param ex_cmd when TRUE pressing : accepts default and starts Ex command
+/// @returns 0 if cancelled, otherwise the nth button (1-indexed).
int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton,
char_u *textfield, int ex_cmd)
{
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 225c66aae1..7fe6469527 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -824,15 +824,12 @@ static bool normal_get_command_count(NormalState *s)
if (s->c == K_DEL || s->c == K_KDEL) {
s->ca.count0 /= 10;
del_from_showcmd(4); // delete the digit and ~@%
+ } else if (s->ca.count0 > 99999999L) {
+ s->ca.count0 = 999999999L;
} else {
s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
}
- if (s->ca.count0 < 0) {
- // overflow
- s->ca.count0 = 999999999L;
- }
-
// Set v:count here, when called from main() and not a stuffed
// command, so that v:count can be used in an expression mapping
// right after the count. Do set it for redo.
@@ -1046,14 +1043,14 @@ static int normal_execute(VimState *state, int key)
// If you give a count before AND after the operator, they are
// multiplied.
if (s->ca.count0) {
- s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)s->ca.opcount);
+ if (s->ca.opcount >= 999999999L / s->ca.count0) {
+ s->ca.count0 = 999999999L;
+ } else {
+ s->ca.count0 *= s->ca.opcount;
+ }
} else {
s->ca.count0 = s->ca.opcount;
}
- if (s->ca.count0 < 0) {
- // overflow
- s->ca.count0 = 999999999L;
- }
}
// Always remember the count. It will be set to zero (on the next call,
@@ -7509,9 +7506,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// overwrites if the old contents is being put.
was_visual = true;
regname = cap->oap->regname;
+ bool save_unnamed = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
- bool clipoverwrite = (regname == '+' || regname == '*')
- && (cb_flags & CB_UNNAMEDMASK);
+ bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
if (regname == 0 || regname == '"' || clipoverwrite
|| ascii_isdigit(regname) || regname == '-') {
// The delete might overwrite the register we want to put, save it first
@@ -7524,6 +7521,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// do_put(), which requires the visual selection to still be active.
if (!VIsual_active || VIsual_mode == 'V' || regname != '.') {
// Now delete the selected text. Avoid messages here.
+ yankreg_T *old_y_previous;
+ if (save_unnamed) {
+ old_y_previous = get_y_previous();
+ }
cap->cmdchar = 'd';
cap->nchar = NUL;
cap->oap->regname = NUL;
@@ -7533,6 +7534,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
msg_silent--;
+ if (save_unnamed) {
+ set_y_previous(old_y_previous);
+ }
+
// delete PUT_LINE_BACKWARD;
cap->oap->regname = regname;
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index b8b639265c..b5c7020dee 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -135,10 +135,18 @@ static char opchars[][3] =
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
-/*
- * Translate a command name into an operator type.
- * Must only be called with a valid operator name!
- */
+yankreg_T *get_y_previous(void)
+{
+ return y_previous;
+}
+
+void set_y_previous(yankreg_T *yreg)
+{
+ y_previous = yreg;
+}
+
+/// Translate a command name into an operator type.
+/// Must only be called with a valid operator name!
int get_op_type(int char1, int char2)
{
int i;
@@ -560,21 +568,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
}
if (spaces > 0) {
- int off;
-
- // Avoid starting halfway through a multi-byte character.
- if (b_insert) {
- off = utf_head_off(oldp, oldp + offset + spaces);
- } else {
- off = (*mb_off_next)(oldp, oldp + offset);
- offset += off;
- }
- spaces -= off;
- count -= off;
+ // avoid copying part of a multi-byte character
+ offset -= utf_head_off(oldp, oldp + offset);
+ }
+ if (spaces < 0) { // can happen when the cursor was moved
+ spaces = 0;
}
assert(count >= 0);
- newp = (char_u *)xmalloc(STRLEN(oldp) + s_len + (size_t)count + 1);
+ // Make sure the allocated size matches what is actually copied below.
+ newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len
+ + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0)
+ + (size_t)count + 1);
// copy up to shifted part
memmove(newp, oldp, (size_t)offset);
@@ -589,14 +594,19 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
offset += (int)s_len;
int skipped = 0;
- if (spaces && !bdp->is_short) {
- // insert post-padding
- memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
- // We're splitting a TAB, don't copy it.
- oldp++;
- // We allowed for that TAB, remember this now
- count++;
- skipped = 1;
+ if (spaces > 0 && !bdp->is_short) {
+ if (*oldp == TAB) {
+ // insert post-padding
+ memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
+ // We're splitting a TAB, don't copy it.
+ oldp++;
+ // We allowed for that TAB, remember this now
+ count++;
+ skipped = 1;
+ } else {
+ // Not a TAB, no extra spaces
+ count = spaces;
+ }
}
if (spaces > 0) {
@@ -1951,11 +1961,14 @@ static int op_replace(oparg_T *oap, int c)
while (ltoreq(curwin->w_cursor, oap->end)) {
n = gchar_cursor();
if (n != NUL) {
- if (utf_char2len(c) > 1 || utf_char2len(n) > 1) {
+ int new_byte_len = utf_char2len(c);
+ int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr());
+
+ if (new_byte_len > 1 || old_byte_len > 1) {
// This is slow, but it handles replacing a single-byte
// with a multi-byte and the other way around.
if (curwin->w_cursor.lnum == oap->end.lnum) {
- oap->end.col += utf_char2len(c) - utf_char2len(n);
+ oap->end.col += new_byte_len - old_byte_len;
}
replace_character(c);
} else {
@@ -2270,6 +2283,7 @@ void op_insert(oparg_T *oap, long count1)
}
t1 = oap->start;
+ const pos_T start_insert = curwin->w_cursor;
(void)edit(NUL, false, (linenr_T)count1);
// When a tab was inserted, and the characters in front of the tab
@@ -2304,23 +2318,18 @@ void op_insert(oparg_T *oap, long count1)
// The user may have moved the cursor before inserting something, try
// to adjust the block for that. But only do it, if the difference
// does not come from indent kicking in.
- if (oap->start.lnum == curbuf->b_op_start_orig.lnum
- && !bd.is_MAX
- && !did_indent) {
+ if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) {
+ const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd);
+
if (oap->op_type == OP_INSERT
&& oap->start.col + oap->start.coladd
!= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
- int t = getviscol2(curbuf->b_op_start_orig.col,
- curbuf->b_op_start_orig.coladd);
oap->start.col = curbuf->b_op_start_orig.col;
pre_textlen -= t - oap->start_vcol;
oap->start_vcol = t;
} else if (oap->op_type == OP_APPEND
- && oap->end.col + oap->end.coladd
- >= curbuf->b_op_start_orig.col
- + curbuf->b_op_start_orig.coladd) {
- int t = getviscol2(curbuf->b_op_start_orig.col,
- curbuf->b_op_start_orig.coladd);
+ && oap->start.col + oap->start.coladd
+ >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
oap->start.col = curbuf->b_op_start_orig.col;
// reset pre_textlen to the value of OP_INSERT
pre_textlen += bd.textlen;
@@ -2368,15 +2377,27 @@ void op_insert(oparg_T *oap, long count1)
firstline = ml_get(oap->start.lnum);
const size_t len = STRLEN(firstline);
colnr_T add = bd.textcol;
+ colnr_T offset = 0; // offset when cursor was moved in insert mode
if (oap->op_type == OP_APPEND) {
add += bd.textlen;
+ // account for pressing cursor in insert mode when '$' was used
+ if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) {
+ offset = start_insert.col - Insstart.col;
+ add -= offset;
+ if (oap->end_vcol > offset) {
+ oap->end_vcol -= offset + 1;
+ } else {
+ // moved outside of the visual block, what to do?
+ return;
+ }
+ }
}
if ((size_t)add > len) {
firstline += len; // short line, point to the NUL
} else {
firstline += add;
}
- ins_len = (long)STRLEN(firstline) - pre_textlen;
+ ins_len = (long)STRLEN(firstline) - pre_textlen - offset;
if (pre_textlen >= 0 && ins_len > 0) {
ins_text = vim_strnsave(firstline, (size_t)ins_len);
// block handled here
@@ -3293,18 +3314,28 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- // insert the new text
+ // Insert the new text.
+ // First check for multiplication overflow.
+ if (yanklen + spaces != 0
+ && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
+
totlen = (size_t)(count * (yanklen + spaces)
+ bd.startspaces + bd.endspaces);
int addcount = (int)totlen + lines_appended;
newp = (char_u *)xmalloc(totlen + oldlen + 1);
+
// copy part up to cursor to new line
ptr = newp;
memmove(ptr, oldp, (size_t)bd.textcol);
ptr += bd.textcol;
+
// may insert some spaces before the new text
memset(ptr, ' ', (size_t)bd.startspaces);
ptr += bd.startspaces;
+
// insert the new text
for (long j = 0; j < count; j++) {
memmove(ptr, y_array[i], (size_t)yanklen);
@@ -3318,9 +3349,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
addcount -= spaces;
}
}
+
// may insert some spaces after the new text
memset(ptr, ' ', (size_t)bd.endspaces);
ptr += bd.endspaces;
+
// move the text after the cursor to the end of the line.
int columns = (int)oldlen - bd.textcol - delcount + 1;
assert(columns >= 0);
@@ -3409,10 +3442,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- do {
+ if (count == 0 || yanklen == 0) {
+ if (VIsual_active) {
+ lnum = end_lnum;
+ }
+ } else if (count > INT_MAX / yanklen) {
+ // multiplication overflow
+ emsg(_(e_resulting_text_too_long));
+ } else {
totlen = (size_t)(count * yanklen);
- if (totlen > 0) {
+ do {
oldp = ml_get(lnum);
+ oldlen = STRLEN(oldp);
if (lnum > start_lnum) {
pos_T pos = {
.lnum = lnum,
@@ -3423,11 +3464,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
col = MAXCOL;
}
}
- if (VIsual_active && col > (int)STRLEN(oldp)) {
+ if (VIsual_active && col > (colnr_T)oldlen) {
lnum++;
continue;
}
- newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1));
+ newp = (char_u *)xmalloc(totlen + oldlen + 1);
memmove(newp, oldp, (size_t)col);
ptr = newp + col;
for (i = 0; i < (size_t)count; i++) {
@@ -3449,14 +3490,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
changed_bytes(lnum, col);
extmark_splice_cols(curbuf, (int)lnum-1, col,
0, (int)totlen, kExtmarkUndo);
- }
- if (VIsual_active) {
- lnum++;
- }
- } while (VIsual_active && lnum <= end_lnum);
+ if (VIsual_active) {
+ lnum++;
+ }
+ } while (VIsual_active && lnum <= end_lnum);
- if (VIsual_active) { // reset lnum to the last visual line
- lnum--;
+ if (VIsual_active) { // reset lnum to the last visual line
+ lnum--;
+ }
}
// put '] at the first byte of the last character
@@ -3724,7 +3765,7 @@ void ex_display(exarg_T *eap)
int name;
char_u *arg = eap->arg;
int clen;
- char_u type[2];
+ int type;
if (arg != NULL && *arg == NUL) {
arg = NULL;
@@ -3737,11 +3778,11 @@ void ex_display(exarg_T *eap)
name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
- type[0] = 'l'; break;
+ type = 'l'; break;
case kMTCharWise:
- type[0] = 'c'; break;
+ type = 'c'; break;
default:
- type[0] = 'b'; break;
+ type = 'b'; break;
}
if (arg != NULL && vim_strchr(arg, name) == NULL) {
@@ -3768,88 +3809,87 @@ void ex_display(exarg_T *eap)
}
if (yb->y_array != NULL) {
- msg_putchar('\n');
- msg_puts(" ");
- msg_putchar(type[0]);
- msg_puts(" ");
- msg_putchar('"');
- msg_putchar(name);
- msg_puts(" ");
-
- int n = Columns - 11;
- for (size_t j = 0; j < yb->y_size && n > 1; j++) {
- if (j) {
- msg_puts_attr("^J", attr);
- n -= 2;
+ bool do_show = false;
+
+ for (size_t j = 0; !do_show && j < yb->y_size; j++) {
+ do_show = !message_filtered(yb->y_array[j]);
+ }
+
+ if (do_show || yb->y_size == 0) {
+ msg_putchar('\n');
+ msg_puts(" ");
+ msg_putchar(type);
+ msg_puts(" ");
+ msg_putchar('"');
+ msg_putchar(name);
+ msg_puts(" ");
+
+ int n = Columns - 11;
+ for (size_t j = 0; j < yb->y_size && n > 1; j++) {
+ if (j) {
+ msg_puts_attr("^J", attr);
+ n -= 2;
+ }
+ for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) {
+ clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen);
+ p += clen - 1;
+ }
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
- p += clen - 1;
+ if (n > 1 && yb->y_type == kMTLineWise) {
+ msg_puts_attr("^J", attr);
}
+ ui_flush(); // show one line at a time
}
- if (n > 1 && yb->y_type == kMTLineWise) {
- msg_puts_attr("^J", attr);
- }
- ui_flush(); // show one line at a time
+ os_breakcheck();
}
- os_breakcheck();
}
- /*
- * display last inserted text
- */
+ // display last inserted text
if ((p = get_last_insert()) != NULL
- && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
+ && !message_filtered(p)) {
msg_puts("\n c \". ");
dis_msg(p, true);
}
- /*
- * display last command line
- */
+ // display last command line
if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(last_cmdline)) {
msg_puts("\n c \": ");
dis_msg(last_cmdline, false);
}
- /*
- * display current file name
- */
+ // display current file name
if (curbuf->b_fname != NULL
- && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int
+ && !message_filtered(curbuf->b_fname)) {
msg_puts("\n c \"% ");
dis_msg(curbuf->b_fname, false);
}
- /*
- * display alternate file name
- */
+ // display alternate file name
if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
char_u *fname;
linenr_T dummy;
- if (buflist_name_nr(0, &fname, &dummy) != FAIL) {
+ if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) {
msg_puts("\n c \"# ");
dis_msg(fname, false);
}
}
- /*
- * display last search pattern
- */
+ // display last search pattern
if (last_search_pat() != NULL
- && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int
+ && !message_filtered(last_search_pat())) {
msg_puts("\n c \"/ ");
dis_msg(last_search_pat(), false);
}
- /*
- * display last used expression
- */
+ // display last used expression
if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(expr_line)) {
msg_puts("\n c \"= ");
dis_msg(expr_line, false);
}
@@ -5671,7 +5711,9 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
// When appending, copy the previous line and free it after.
size_t extra = append ? STRLEN(pp[--lnum]) : 0;
char_u *s = xmallocz(line_len + extra);
- memcpy(s, pp[lnum], extra);
+ if (extra > 0) {
+ memcpy(s, pp[lnum], extra);
+ }
memcpy(s + extra, start, line_len);
size_t s_len = extra + line_len;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index b9fed8b378..d97a22c342 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1984,10 +1984,9 @@ static void didset_options(void)
(void)did_set_spell_option(true);
// set cedit_key
(void)check_cedit();
- briopt_check(curwin);
// initialize the table for 'breakat'.
fill_breakat_flags();
- fill_culopt_flags(NULL, curwin);
+ didset_window_options(curwin);
}
// More side effects of setting options.
@@ -4278,7 +4277,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
// Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" infact sets the global
+ // a global-only option setting the "local value" in fact sets the global
// value (since there is only one value).
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
@@ -6174,6 +6173,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
+ didset_window_options(wp_to);
}
/// Copy the options from one winopt_T to another.
@@ -6232,6 +6232,9 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_fcs = vim_strsave(from->wo_fcs);
to->wo_lcs = vim_strsave(from->wo_lcs);
to->wo_winbl = from->wo_winbl;
+
+ // Copy the script context so that we know were the value was last set.
+ memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx));
check_winopt(to); // don't want NULL pointers
}
@@ -6304,11 +6307,30 @@ void didset_window_options(win_T *wp)
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
+/// Index into the options table for a buffer-local option enum.
+static int buf_opt_idx[BV_COUNT];
+#define COPY_OPT_SCTX(buf, bv) buf->b_p_script_ctx[bv] = options[buf_opt_idx[bv]].last_set
+
+/// Initialize buf_opt_idx[] if not done already.
+static void init_buf_opt_idx(void)
+{
+ static int did_init_buf_opt_idx = false;
+
+ if (did_init_buf_opt_idx) {
+ return;
+ }
+ did_init_buf_opt_idx = true;
+ for (int i = 0; options[i].fullname != NULL; i++) {
+ if (options[i].indir & PV_BUF) {
+ buf_opt_idx[options[i].indir & PV_MASK] = i;
+ }
+ }
+}
/// Copy global option values to local options for one buffer.
/// Used when creating a new buffer and sometimes when entering a buffer.
/// flags:
-/// BCO_ENTER We will enter the buf buffer.
+/// BCO_ENTER We will enter the buffer "buf".
/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
/// appropriate.
/// BCO_NOHELP Don't copy the values to a help buffer.
@@ -6344,11 +6366,12 @@ void buf_copy_options(buf_T *buf, int flags)
}
if (should_copy || (flags & BCO_ALWAYS)) {
- /* Don't copy the options specific to a help buffer when
- * BCO_NOHELP is given or the options were initialized already
- * (jumping back to a help file with CTRL-T or CTRL-O) */
- dont_do_help = ((flags & BCO_NOHELP) && buf->b_help)
- || buf->b_p_initialized;
+ memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx));
+ init_buf_opt_idx();
+ // Don't copy the options specific to a help buffer when
+ // BCO_NOHELP is given or the options were initialized already
+ // (jumping back to a help file with CTRL-T or CTRL-O)
+ dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized;
if (dont_do_help) { // don't free b_p_isk
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
@@ -6380,35 +6403,58 @@ void buf_copy_options(buf_T *buf, int flags)
}
buf->b_p_ai = p_ai;
+ COPY_OPT_SCTX(buf, BV_AI);
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
+ COPY_OPT_SCTX(buf, BV_SW);
buf->b_p_scbk = p_scbk;
+ COPY_OPT_SCTX(buf, BV_SCBK);
buf->b_p_tw = p_tw;
+ COPY_OPT_SCTX(buf, BV_TW);
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;
buf->b_p_wm = p_wm;
+ COPY_OPT_SCTX(buf, BV_WM);
buf->b_p_wm_nopaste = p_wm_nopaste;
buf->b_p_wm_nobin = p_wm_nobin;
buf->b_p_bin = p_bin;
+ COPY_OPT_SCTX(buf, BV_BIN);
buf->b_p_bomb = p_bomb;
+ COPY_OPT_SCTX(buf, BV_BOMB);
buf->b_p_et = p_et;
+ COPY_OPT_SCTX(buf, BV_ET);
buf->b_p_fixeol = p_fixeol;
+ COPY_OPT_SCTX(buf, BV_FIXEOL);
buf->b_p_et_nobin = p_et_nobin;
buf->b_p_et_nopaste = p_et_nopaste;
buf->b_p_ml = p_ml;
+ COPY_OPT_SCTX(buf, BV_ML);
buf->b_p_ml_nobin = p_ml_nobin;
buf->b_p_inf = p_inf;
- buf->b_p_swf = cmdmod.noswapfile ? false : p_swf;
+ COPY_OPT_SCTX(buf, BV_INF);
+ if (cmdmod.noswapfile) {
+ buf->b_p_swf = false;
+ } else {
+ buf->b_p_swf = p_swf;
+ COPY_OPT_SCTX(buf, BV_SWF);
+ }
buf->b_p_cpt = vim_strsave(p_cpt);
+ COPY_OPT_SCTX(buf, BV_CPT);
#ifdef BACKSLASH_IN_FILENAME
buf->b_p_csl = vim_strsave(p_csl);
+ COPY_OPT_SCTX(buf, BV_CSL);
#endif
buf->b_p_cfu = vim_strsave(p_cfu);
+ COPY_OPT_SCTX(buf, BV_CFU);
buf->b_p_ofu = vim_strsave(p_ofu);
+ COPY_OPT_SCTX(buf, BV_OFU);
buf->b_p_tfu = vim_strsave(p_tfu);
+ COPY_OPT_SCTX(buf, BV_TFU);
buf->b_p_sts = p_sts;
+ COPY_OPT_SCTX(buf, BV_STS);
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = vim_strsave(p_vsts);
+ COPY_OPT_SCTX(buf, BV_VSTS);
if (p_vsts && p_vsts != empty_option) {
(void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
@@ -6418,42 +6464,68 @@ void buf_copy_options(buf_T *buf, int flags)
? vim_strsave(p_vsts_nopaste)
: NULL;
buf->b_p_com = vim_strsave(p_com);
+ COPY_OPT_SCTX(buf, BV_COM);
buf->b_p_cms = vim_strsave(p_cms);
+ COPY_OPT_SCTX(buf, BV_CMS);
buf->b_p_fo = vim_strsave(p_fo);
+ COPY_OPT_SCTX(buf, BV_FO);
buf->b_p_flp = vim_strsave(p_flp);
+ COPY_OPT_SCTX(buf, BV_FLP);
buf->b_p_nf = vim_strsave(p_nf);
+ COPY_OPT_SCTX(buf, BV_NF);
buf->b_p_mps = vim_strsave(p_mps);
+ COPY_OPT_SCTX(buf, BV_MPS);
buf->b_p_si = p_si;
+ COPY_OPT_SCTX(buf, BV_SI);
buf->b_p_channel = 0;
buf->b_p_ci = p_ci;
+ COPY_OPT_SCTX(buf, BV_CI);
buf->b_p_cin = p_cin;
+ COPY_OPT_SCTX(buf, BV_CIN);
buf->b_p_cink = vim_strsave(p_cink);
+ COPY_OPT_SCTX(buf, BV_CINK);
buf->b_p_cino = vim_strsave(p_cino);
+ COPY_OPT_SCTX(buf, BV_CINO);
// Don't copy 'filetype', it must be detected
buf->b_p_ft = empty_option;
buf->b_p_pi = p_pi;
+ COPY_OPT_SCTX(buf, BV_PI);
buf->b_p_cinw = vim_strsave(p_cinw);
+ COPY_OPT_SCTX(buf, BV_CINW);
buf->b_p_lisp = p_lisp;
+ COPY_OPT_SCTX(buf, BV_LISP);
// Don't copy 'syntax', it must be set
buf->b_p_syn = empty_option;
buf->b_p_smc = p_smc;
+ COPY_OPT_SCTX(buf, BV_SMC);
buf->b_s.b_syn_isk = empty_option;
buf->b_s.b_p_spc = vim_strsave(p_spc);
+ COPY_OPT_SCTX(buf, BV_SPC);
(void)compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = vim_strsave(p_spf);
+ COPY_OPT_SCTX(buf, BV_SPF);
buf->b_s.b_p_spl = vim_strsave(p_spl);
+ COPY_OPT_SCTX(buf, BV_SPL);
buf->b_s.b_p_spo = vim_strsave(p_spo);
+ COPY_OPT_SCTX(buf, BV_SPO);
buf->b_p_inde = vim_strsave(p_inde);
+ COPY_OPT_SCTX(buf, BV_INDE);
buf->b_p_indk = vim_strsave(p_indk);
+ COPY_OPT_SCTX(buf, BV_INDK);
buf->b_p_fp = empty_option;
buf->b_p_fex = vim_strsave(p_fex);
+ COPY_OPT_SCTX(buf, BV_FEX);
buf->b_p_sua = vim_strsave(p_sua);
+ COPY_OPT_SCTX(buf, BV_SUA);
buf->b_p_keymap = vim_strsave(p_keymap);
+ COPY_OPT_SCTX(buf, BV_KMAP);
buf->b_kmap_state |= KEYMAP_INIT;
// This isn't really an option, but copying the langmap and IME
// state from the current buffer is better than resetting it.
buf->b_p_iminsert = p_iminsert;
+ COPY_OPT_SCTX(buf, BV_IMI);
buf->b_p_imsearch = p_imsearch;
+ COPY_OPT_SCTX(buf, BV_IMS);
// options that are normally global but also have a local value
// are not copied, start using the global value
@@ -6473,11 +6545,14 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_def = empty_option;
buf->b_p_inc = empty_option;
buf->b_p_inex = vim_strsave(p_inex);
+ COPY_OPT_SCTX(buf, BV_INEX);
buf->b_p_dict = empty_option;
buf->b_p_tsr = empty_option;
buf->b_p_tsrfu = empty_option;
buf->b_p_qe = vim_strsave(p_qe);
+ COPY_OPT_SCTX(buf, BV_QE);
buf->b_p_udf = p_udf;
+ COPY_OPT_SCTX(buf, BV_UDF);
buf->b_p_lw = empty_option;
buf->b_p_menc = empty_option;
@@ -6496,9 +6571,12 @@ void buf_copy_options(buf_T *buf, int flags)
}
} else {
buf->b_p_isk = vim_strsave(p_isk);
+ COPY_OPT_SCTX(buf, BV_ISK);
did_isk = true;
buf->b_p_ts = p_ts;
+ COPY_OPT_SCTX(buf, BV_TS);
buf->b_p_vts = vim_strsave(p_vts);
+ COPY_OPT_SCTX(buf, BV_VTS);
if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
(void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
@@ -6509,6 +6587,7 @@ void buf_copy_options(buf_T *buf, int flags)
clear_string_option(&buf->b_p_bt);
}
buf->b_p_ma = p_ma;
+ COPY_OPT_SCTX(buf, BV_MA);
}
}
@@ -8046,7 +8125,6 @@ int win_signcol_count(win_T *wp)
/// Return the number of requested sign columns, based on user / configuration.
int win_signcol_configured(win_T *wp, int *is_fixed)
{
- int minimum = 0, maximum = 1, needed_signcols;
const char *scl = (const char *)wp->w_p_scl;
if (is_fixed) {
@@ -8059,7 +8137,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
&& (wp->w_p_nu || wp->w_p_rnu)))) {
return 0;
}
- needed_signcols = buf_signcols(wp->w_buffer);
// yes or yes
if (!strncmp(scl, "yes:", 4)) {
@@ -8075,6 +8152,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
*is_fixed = 0;
}
+ int minimum = 0, maximum = 1;
+
if (!strncmp(scl, "auto:", 5)) {
// Variable depending on a configuration
maximum = scl[5] - '0';
@@ -8085,7 +8164,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
}
}
- int ret = MAX(minimum, MIN(maximum, needed_signcols));
+ int needed_signcols = buf_signcols(wp->w_buffer, maximum);
+ int ret = MAX(minimum, needed_signcols);
assert(ret <= SIGN_SHOW_MAX);
return ret;
}
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 7f7f941e26..d3aa5e5bf2 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -642,8 +642,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff,
} else if (path_end >= path + wildoff
&& (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
#ifndef WIN32
- || (!p_fic && (flags & EW_ICASE)
- && isalpha(utf_ptr2char(path_end)))
+ || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end)))
#endif
)) {
e = p;
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index d17e27906e..51991ed314 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -16,7 +16,9 @@ typedef int colnr_T;
/// Maximal (invalid) line number
enum { MAXLNUM = 0x7fffffff, };
/// Maximal column number
-enum { MAXCOL = INT_MAX, };
+/// MAXCOL used to be INT_MAX, but with 64 bit ints that results in running
+/// out of memory when trying to allocate a very long line.
+enum { MAXCOL = 0x7fffffff, };
// Minimum line number
enum { MINLNUM = 1, };
// minimum column number
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 0196e05455..7e29aed51b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -131,7 +131,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 11 // maximum number of % recognized
+#define FMT_PATTERNS 13 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -209,6 +209,17 @@ typedef struct {
bool valid;
} qffields_T;
+/// :vimgrep command arguments
+typedef struct vgr_args_S {
+ long tomatch; ///< maximum number of matches to find
+ char_u *spat; ///< search pattern
+ int flags; ///< search modifier
+ char_u **fnames; ///< list of files to search
+ int fcount; ///< number of files
+ regmmatch_T regmatch; ///< compiled search pattern
+ char_u *qf_title; ///< quickfix list title
+} vgr_args_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
@@ -321,22 +332,27 @@ int qf_init(win_T *wp, const char_u *restrict efile, char_u *restrict errorforma
// Maximum number of bytes allowed per line while reading an errorfile.
static const size_t LINE_MAXLEN = 4096;
+/// Patterns used. Keep in sync with qf_parse_fmt[].
static struct fmtpattern {
char_u convchar;
char *pattern;
} fmt_pat[FMT_PATTERNS] =
{
- { 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" },
- { 'l', "\\d\\+" },
- { 'c', "\\d\\+" },
- { 't', "." },
- { 'm', ".\\+" },
- { 'r', ".*" },
- { 'p', "[- .]*"}, // NOLINT(whitespace/tab)
- { 'v', "\\d\\+" },
- { 's', ".\\+" },
- { 'o', ".\\+" }
+ { 'f', ".\\+" }, // only used when at end
+ { 'n', "\\d\\+" }, // 1
+ { 'l', "\\d\\+" }, // 2
+ { 'e', "\\d\\+" }, // 3
+ { 'c', "\\d\\+" }, // 4
+ { 'k', "\\d\\+" }, // 5
+ { 't', "." }, // 6
+#define FMT_PATTERN_M 7
+ { 'm', ".\\+" }, // 7
+#define FMT_PATTERN_R 8
+ { 'r', ".*" }, // 8
+ { 'p', "[- \t.]*" }, // 9
+ { 'v', "\\d\\+" }, // 10
+ { 's', ".\\+" }, // 11
+ { 'o', ".\\+" } // 12
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -352,9 +368,9 @@ static char_u *efmpat_to_regpat(const char_u *efmpat, char_u *regpat, efm_T *efm
semsg(_("E372: Too many %%%c in format string"), *efmpat);
return NULL;
}
- if ((idx && idx < 6
+ if ((idx && idx < FMT_PATTERN_R
&& vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
- || (idx == 6
+ || (idx == FMT_PATTERN_R
&& vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
semsg(_("E373: Unexpected %%%c in format string"), *efmpat);
return NULL;
@@ -1277,7 +1293,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
-/// Parse the match for line number (%l') pattern in regmatch.
+/// Parse the match for line number ('%l') pattern in regmatch.
/// Return the matched value in "fields->lnum".
static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
{
@@ -1288,6 +1304,17 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_lnum = atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for column number ('%c') pattern in regmatch.
/// Return the matched value in "fields->col".
static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1299,6 +1326,17 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_col = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for error type ('%t') pattern in regmatch.
/// Return the matched value in "fields->type".
static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1431,14 +1469,17 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// 'errorformat' format pattern parser functions.
/// The '%f' and '%r' formats are parsed differently from other formats.
/// See qf_parse_match() for details.
+/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
- NULL,
+ NULL, // %f
qf_parse_fmt_n,
qf_parse_fmt_l,
+ qf_parse_fmt_e,
qf_parse_fmt_c,
+ qf_parse_fmt_k,
qf_parse_fmt_t,
qf_parse_fmt_m,
- NULL,
+ NULL, // %r
qf_parse_fmt_p,
qf_parse_fmt_v,
qf_parse_fmt_s,
@@ -1474,13 +1515,13 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, regma
midx = (int)fmt_ptr->addr[i];
if (i == 0 && midx > 0) { // %f
status = qf_parse_fmt_f(regmatch, midx, fields, idx);
- } else if (i == 5) {
+ } else if (i == FMT_PATTERN_M) {
if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
qf_parse_fmt_plus(linebuf, linelen, fields);
} else if (midx > 0) { // %m
status = qf_parse_fmt_m(regmatch, midx, fields);
}
- } else if (i == 6 && midx > 0) { // %r
+ } else if (i == FMT_PATTERN_R && midx > 0) { // %r
status = qf_parse_fmt_r(regmatch, midx, tail);
} else if (midx > 0) { // others
status = (qf_parse_fmt[i])(regmatch, midx, fields);
@@ -1625,10 +1666,16 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
if (!qfprev->qf_lnum) {
qfprev->qf_lnum = fields->lnum;
}
+ if (!qfprev->qf_end_lnum) {
+ qfprev->qf_end_lnum = fields->end_lnum;
+ }
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
qfprev->qf_viscol = fields->use_viscol;
}
+ if (!qfprev->qf_end_col) {
+ qfprev->qf_end_col = fields->end_col;
+ }
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -2975,7 +3022,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo
if (retval != OK) {
if (opened_window) {
- win_close(curwin, true); // Close opened window
+ win_close(curwin, true, false); // Close opened window
}
if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) {
// Couldn't open file, so put index back where it was. This could
@@ -3530,7 +3577,7 @@ void ex_cclose(exarg_T *eap)
// Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
- win_close(win, false);
+ win_close(win, false, false);
}
}
@@ -3604,7 +3651,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
// win_split, so add a check to ensure that the win is still here
if (IS_LL_STACK(qi) && !win_valid(win)) {
// close the window that was supposed to be for the loclist
- win_close(curwin, false);
+ win_close(curwin, false, false);
return FAIL;
}
@@ -4849,11 +4896,12 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, const pos_T *pos
/// Get the nth quickfix entry below the specified entry. Searches forward in
/// the list. If linewise is true, then treat multiple entries on a single line
/// as one.
-static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr)
+static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewise, int *errornr)
FUNC_ATTR_NONNULL_ALL
{
+ qfline_T *entry = entry_arg;
+
while (n-- > 0 && !got_int) {
- // qfline_T *first_entry = entry;
int first_errornr = *errornr;
if (linewise) {
@@ -4864,9 +4912,6 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, i
if (entry->qf_next == NULL
|| entry->qf_next->qf_fnum != entry->qf_fnum) {
if (linewise) {
- // If multiple entries are on the same line, then use the first
- // entry
- // entry = first_entry;
*errornr = first_errornr;
}
break;
@@ -5194,49 +5239,93 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *ti
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
-static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmmatch_T *regmatch,
- long *tomatch, int duplicate_name, int flags)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
+static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u *spat,
+ regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6)
{
bool found_match = false;
for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
- while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
- NULL) > 0) {
- // Pass the buffer number so that it gets used even for a
- // dummy buffer, unless duplicate_name is set, then the
- // buffer will be wiped out below.
- if (qf_add_entry(qfl,
- NULL, // dir
- fname,
- NULL,
- duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf, regmatch->startpos[0].lnum + lnum,
- false),
- regmatch->startpos[0].lnum + lnum,
- regmatch->endpos[0].lnum + lnum,
- regmatch->startpos[0].col + 1,
- regmatch->endpos[0].col + 1,
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 0, // type
- true) // valid
- == QF_FAIL) {
- got_int = true;
- break;
- }
- found_match = true;
- if (--*tomatch == 0) {
- break;
- }
- if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
- break;
+ if (!(flags & VGR_FUZZY)) {
+ // Regular expression match
+ while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, NULL) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false),
+ regmatch->startpos[0].lnum + lnum,
+ regmatch->endpos[0].lnum + lnum,
+ regmatch->startpos[0].col + 1,
+ regmatch->endpos[0].col + 1,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
+ break;
+ }
+ col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
+ if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ break;
+ }
}
- col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
- if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
- break;
+ } else {
+ const size_t pat_len = STRLEN(spat);
+ char_u *const str = ml_get_buf(buf, lnum, false);
+ int score;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+ const size_t sz = sizeof(matches) / sizeof(matches[0]);
+
+ // Fuzzy string match
+ while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ str,
+ lnum,
+ 0,
+ (colnr_T)matches[0] + col + 1,
+ 0,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0) {
+ break;
+ }
+ col = (colnr_T)matches[pat_len - 1] + col + 1;
+ if (col > (colnr_T)STRLEN(str)) {
+ break;
+ }
}
}
line_breakcheck();
@@ -5249,7 +5338,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmma
}
/// Jump to the first match and update the directory.
-static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy,
buf_T *first_match_buf, char_u *target_dir)
{
buf_T *buf = curbuf;
@@ -5284,104 +5373,72 @@ static bool existing_swapfile(const buf_T *buf)
return false;
}
-// ":vimgrep {pattern} file(s)"
-// ":vimgrepadd {pattern} file(s)"
-// ":lvimgrep {pattern} file(s)"
-// ":lvimgrepadd {pattern} file(s)"
-void ex_vimgrep(exarg_T *eap)
+/// Process :vimgrep command arguments. The command syntax is:
+///
+/// :{count}vimgrep /{pattern}/[g][j] {file} ...
+static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
{
- regmmatch_T regmatch;
- int fcount;
- char_u **fnames;
- char_u *fname;
- char_u *s;
- char_u *p;
- int fi;
- qf_list_T *qfl;
- win_T *wp = NULL;
- buf_T *buf;
- int duplicate_name = FALSE;
- int using_dummy;
- int redraw_for_dummy = FALSE;
- int found_match;
- buf_T *first_match_buf = NULL;
- time_t seconds = 0;
- aco_save_T aco;
- int flags = 0;
- long tomatch;
- char_u *dirname_start = NULL;
- char_u *dirname_now = NULL;
- char_u *target_dir = NULL;
- char_u *au_name = NULL;
-
- au_name = vgr_get_auname(eap->cmdidx);
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
- curbuf->b_fname, true, curbuf)) {
- if (aborting()) {
- return;
- }
- }
+ memset(args, 0, sizeof(*args));
- qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ args->regmatch.regprog = NULL;
+ args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
if (eap->addr_count > 0) {
- tomatch = eap->line2;
+ args->tomatch = eap->line2;
} else {
- tomatch = MAXLNUM;
+ args->tomatch = MAXLNUM;
}
// Get the search pattern: either white-separated or enclosed in //
- regmatch.regprog = NULL;
- char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
- p = skip_vimgrep_pat(eap->arg, &s, &flags);
+ char_u *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags);
if (p == NULL) {
emsg(_(e_invalpat));
- goto theend;
+ return FAIL;
}
- vgr_init_regmatch(&regmatch, s);
- if (regmatch.regprog == NULL) {
- goto theend;
+ vgr_init_regmatch(&args->regmatch, args->spat);
+ if (args->regmatch.regprog == NULL) {
+ return FAIL;
}
p = skipwhite(p);
if (*p == NUL) {
emsg(_("E683: File name missing or invalid pattern"));
- goto theend;
- }
-
- if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
- && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qf_stack_empty(qi)) {
- // make place for a new list
- qf_new_list(qi, title);
+ return FAIL;
}
// Parse the list of arguments, wildcards have already been expanded.
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
- goto theend;
+ if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) {
+ return FAIL;
}
- if (fcount == 0) {
+ if (args->fcount == 0) {
emsg(_(e_nomatch));
- goto theend;
+ return FAIL;
}
- dirname_start = xmalloc(MAXPATHL);
- dirname_now = xmalloc(MAXPATHL);
+ return OK;
+}
+
+/// Search for a pattern in a list of files and populate the quickfix list with
+/// the matches.
+static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args,
+ bool *redraw_for_dummy, buf_T **first_match_buf,
+ char_u **target_dir)
+{
+ int status = FAIL;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ bool duplicate_name = false;
+
+ char_u *dirname_start = xmalloc(MAXPATHL);
+ char_u *dirname_now = xmalloc(MAXPATHL);
// Remember the current directory, because a BufRead autocommand that does
// ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
- incr_quickfix_busy();
-
- // Remember the current quickfix list identifier, so that we can check for
- // autocommands changing the current quickfix list.
- unsigned save_qfid = qf_get_curlist(qi)->qf_id;
-
- seconds = (time_t)0;
- for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
- fname = path_try_shorten_fname(fnames[fi]);
+ time_t seconds = (time_t)0;
+ for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
+ char_u *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
// Display the file name every second or so, show the user we are
// working on it.
@@ -5389,13 +5446,13 @@ void ex_vimgrep(exarg_T *eap)
vgr_display_fname(fname);
}
- buf = buflist_findname_exp(fnames[fi]);
+ buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]);
+ bool using_dummy;
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
// Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
- using_dummy = TRUE;
- redraw_for_dummy = TRUE;
-
+ using_dummy = true;
+ *redraw_for_dummy = true;
buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
} else {
// Use existing, loaded buffer.
@@ -5404,11 +5461,10 @@ void ex_vimgrep(exarg_T *eap)
// Check whether the quickfix list is still valid. When loading a
// buffer above, autocommands might have changed the quickfix list.
- if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) {
- FreeWild(fcount, fnames);
- decr_quickfix_busy();
+ if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) {
goto theend;
}
+
save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
@@ -5418,13 +5474,18 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qf_get_curlist(qi),
- fname, buf, &regmatch, &tomatch,
- duplicate_name, flags);
+ bool found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname,
+ buf,
+ cmd_args->spat,
+ &cmd_args->regmatch,
+ &cmd_args->tomatch,
+ duplicate_name,
+ cmd_args->flags);
if (using_dummy) {
- if (found_match && first_match_buf == NULL) {
- first_match_buf = buf;
+ if (found_match && *first_match_buf == NULL) {
+ *first_match_buf = buf;
}
if (duplicate_name) {
// Never keep a dummy buffer if there is another buffer
@@ -5444,8 +5505,8 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf
- || (flags & VGR_NOJUMP)
+ } else if (buf != *first_match_buf
+ || (cmd_args->flags & VGR_NOJUMP)
|| existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
@@ -5460,16 +5521,17 @@ void ex_vimgrep(exarg_T *eap)
// If the buffer is still loaded we need to use the
// directory we jumped to below.
- if (buf == first_match_buf
- && target_dir == NULL
+ if (buf == *first_match_buf
+ && *target_dir == NULL
&& STRCMP(dirname_start, dirname_now) != 0) {
- target_dir = vim_strsave(dirname_now);
+ *target_dir = vim_strsave(dirname_now);
}
// The buffer is still loaded, the Filetype autocommands
// need to be done now, in that buffer. And the modelines
// need to be done (again). But not the window-local
// options!
+ aco_save_T aco;
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf);
do_modelines(OPT_NOWIN);
@@ -5479,9 +5541,58 @@ void ex_vimgrep(exarg_T *eap)
}
}
- FreeWild(fcount, fnames);
+ status = OK;
- qfl = qf_get_curlist(qi);
+theend:
+ xfree(dirname_now);
+ xfree(dirname_start);
+ return status;
+}
+
+/// ":vimgrep {pattern} file(s)"
+/// ":vimgrepadd {pattern} file(s)"
+/// ":lvimgrep {pattern} file(s)"
+/// ":lvimgrepadd {pattern} file(s)"
+void ex_vimgrep(exarg_T *eap)
+{
+ char_u *au_name = vgr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
+ curbuf->b_fname, true, curbuf)) {
+ if (aborting()) {
+ return;
+ }
+ }
+
+ win_T *wp = NULL;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ char_u *target_dir = NULL;
+ vgr_args_T args;
+ if (vgr_process_args(eap, &args) == FAIL) {
+ goto theend;
+ }
+
+ if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
+ && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
+ || qf_stack_empty(qi)) {
+ // make place for a new list
+ qf_new_list(qi, args.qf_title);
+ }
+
+ incr_quickfix_busy();
+
+ bool redraw_for_dummy = false;
+ buf_T *first_match_buf = NULL;
+ int status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, &first_match_buf, &target_dir);
+
+ if (status != OK) {
+ FreeWild(args.fcount, args.fnames);
+ decr_quickfix_busy();
+ goto theend;
+ }
+
+ FreeWild(args.fcount, args.fnames);
+
+ qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
@@ -5489,26 +5600,28 @@ void ex_vimgrep(exarg_T *eap)
qf_update_buffer(qi, NULL);
+ // Remember the current quickfix list identifier, so that we can check for
+ // autocommands changing the current quickfix list.
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf);
}
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)
- || qf_restore_list(qi, save_qfid) == FAIL) {
+ if (!qflist_valid(wp, save_qfid) || qf_restore_list(qi, save_qfid) == FAIL) {
decr_quickfix_busy();
goto theend;
}
// Jump to first match.
if (!qf_list_empty(qf_get_curlist(qi))) {
- if ((flags & VGR_NOJUMP) == 0) {
- vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
- target_dir);
+ if ((args.flags & VGR_NOJUMP) == 0) {
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir);
}
} else {
- semsg(_(e_nomatch2), s);
+ semsg(_(e_nomatch2), args.spat);
}
decr_quickfix_busy();
@@ -5520,11 +5633,9 @@ void ex_vimgrep(exarg_T *eap)
}
theend:
- xfree(title);
- xfree(dirname_now);
- xfree(dirname_start);
+ xfree(args.qf_title);
xfree(target_dir);
- vim_regfree(regmatch.regprog);
+ vim_regfree(args.regmatch.regprog);
}
// Restore current working directory to "dirname_start" if they differ, taking
@@ -5656,7 +5767,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
if (firstwin->w_next != NULL) {
for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
if (wp->w_buffer == buf) {
- if (win_close(wp, false) == OK) {
+ if (win_close(wp, false, false) == OK) {
did_one = true;
}
break;
diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h
index f5178e332a..0da43e436c 100644
--- a/src/nvim/quickfix.h
+++ b/src/nvim/quickfix.h
@@ -7,6 +7,7 @@
// flags for skip_vimgrep_pat()
#define VGR_GLOBAL 1
#define VGR_NOJUMP 2
+#define VGR_FUZZY 4
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.h.generated.h"
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 6b2a2afa41..15fb6901cc 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -794,7 +794,7 @@ static void win_update(win_T *wp, Providers *providers)
// If we can compute a change in the automatic sizing of the sign column
// under 'signcolumn=auto:X' and signs currently placed in the buffer, better
// figuring it out here so we can redraw the entire screen for it.
- buf_signcols(buf);
+ win_signcol_count(wp);
type = wp->w_redr_type;
@@ -2813,7 +2813,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// in 'lnum', then display the sign instead of the line
// number.
if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
- && num_signs > 0) {
+ && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) {
int count = win_signcol_count(wp);
get_sign_display_info(true, wp, lnum, sattrs, row,
startrow, filler_lines, filler_todo, count,
@@ -3753,34 +3753,30 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
c = wp->w_p_lcs_chars.tab1;
p = xmalloc(len + 1);
- if (p == NULL) {
- n_extra = 0;
- } else {
- memset(p, ' ', len);
- p[len] = NUL;
- xfree(p_extra_free);
- p_extra_free = p;
- for (i = 0; i < tab_len; i++) {
- if (*p == NUL) {
- tab_len = i;
- break;
- }
- int lcs = wp->w_p_lcs_chars.tab2;
-
- // if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
- }
- p += utf_char2bytes(lcs, p);
- n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
+ memset(p, ' ', len);
+ p[len] = NUL;
+ xfree(p_extra_free);
+ p_extra_free = p;
+ for (i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
}
- p_extra = p_extra_free;
+ int lcs = wp->w_p_lcs_chars.tab2;
- // n_extra will be increased by FIX_FOX_BOGUSCOLS
- // macro below, so need to adjust for that here
- if (vcol_off > 0) {
- n_extra -= vcol_off;
+ // if tab3 is given, use it for the last char
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
}
+ p += utf_char2bytes(lcs, p);
+ n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
+ }
+ p_extra = p_extra_free;
+
+ // n_extra will be increased by FIX_FOX_BOGUSCOLS
+ // macro below, so need to adjust for that here
+ if (vcol_off > 0) {
+ n_extra -= vcol_off;
}
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 93180f97fe..682fa417a9 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -4764,6 +4764,536 @@ the_end:
restore_last_search_pattern();
}
+/// Fuzzy string matching
+///
+/// Ported from the lib_fts library authored by Forrest Smith.
+/// https://github.com/forrestthewoods/lib_fts/tree/master/code
+///
+/// The following blog describes the fuzzy matching algorithm:
+/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
+///
+/// Each matching string is assigned a score. The following factors are checked:
+/// - Matched letter
+/// - Unmatched letter
+/// - Consecutively matched letters
+/// - Proximity to start
+/// - Letter following a separator (space, underscore)
+/// - Uppercase letter following lowercase (aka CamelCase)
+///
+/// Matched letters are good. Unmatched letters are bad. Matching near the start
+/// is good. Matching the first letter in the middle of a phrase is good.
+/// Matching the uppercase letters in camel case entries is good.
+///
+/// The score assigned for each factor is explained below.
+/// File paths are different from file names. File extensions may be ignorable.
+/// Single words care about consecutive matches but not separators or camel
+/// case.
+/// Score starts at 100
+/// Matched letter: +0 points
+/// Unmatched letter: -1 point
+/// Consecutive match bonus: +15 points
+/// First letter bonus: +15 points
+/// Separator bonus: +30 points
+/// Camel case bonus: +30 points
+/// Unmatched leading letter: -5 points (max: -15)
+///
+/// There is some nuance to this. Scores don’t have an intrinsic meaning. The
+/// score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
+/// lower minimum score due to unmatched letter penalty. Longer search patterns
+/// have a higher maximum score due to match bonuses.
+///
+/// Separator and camel case bonus is worth a LOT. Consecutive matches are worth
+/// quite a bit.
+///
+/// There is a penalty if you DON’T match the first three letters. Which
+/// effectively rewards matching near the start. However there’s no difference
+/// in matching between the middle and end.
+///
+/// There is not an explicit bonus for an exact match. Unmatched letters receive
+/// a penalty. So shorter strings and closer matches are worth more.
+typedef struct {
+ int idx; ///< used for stable sort
+ listitem_T *item;
+ int score;
+ list_T *lmatchpos;
+} fuzzyItem_T;
+
+/// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
+/// matching a whole word is preferred.
+#define SEQUENTIAL_BONUS 40
+/// bonus if match occurs after a path separator
+#define PATH_SEPARATOR_BONUS 30
+/// bonus if match occurs after a word separator
+#define WORD_SEPARATOR_BONUS 25
+/// bonus if match is uppercase and prev is lower
+#define CAMEL_BONUS 30
+/// bonus if the first letter is matched
+#define FIRST_LETTER_BONUS 15
+/// penalty applied for every letter in str before the first match
+#define LEADING_LETTER_PENALTY -5
+/// maximum penalty for leading letters
+#define MAX_LEADING_LETTER_PENALTY -15
+/// penalty for every letter that doesn't match
+#define UNMATCHED_LETTER_PENALTY -1
+/// penalty for gap in matching positions (-2 * k)
+#define GAP_PENALTY -2
+/// Score for a string that doesn't fuzzy match the pattern
+#define SCORE_NONE -9999
+
+#define FUZZY_MATCH_RECURSION_LIMIT 10
+
+/// Compute a score for a fuzzy matched string. The matching character locations
+/// are in 'matches'.
+static int fuzzy_match_compute_score(const char_u *const str, const int strSz,
+ const uint32_t *const matches, const int numMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ assert(numMatches > 0); // suppress clang "result of operation is garbage"
+ // Initialize score
+ int score = 100;
+
+ // Apply leading letter penalty
+ int penalty = LEADING_LETTER_PENALTY * matches[0];
+ if (penalty < MAX_LEADING_LETTER_PENALTY) {
+ penalty = MAX_LEADING_LETTER_PENALTY;
+ }
+ score += penalty;
+
+ // Apply unmatched penalty
+ const int unmatched = strSz - numMatches;
+ score += UNMATCHED_LETTER_PENALTY * unmatched;
+
+ // Apply ordering bonuses
+ for (int i = 0; i < numMatches; i++) {
+ const uint32_t currIdx = matches[i];
+
+ if (i > 0) {
+ const uint32_t prevIdx = matches[i - 1];
+
+ // Sequential
+ if (currIdx == prevIdx + 1) {
+ score += SEQUENTIAL_BONUS;
+ } else {
+ score += GAP_PENALTY * (currIdx - prevIdx);
+ }
+ }
+
+ // Check for bonuses based on neighbor character value
+ if (currIdx > 0) {
+ // Camel case
+ const char_u *p = str;
+ int neighbor;
+
+ for (uint32_t sidx = 0; sidx < currIdx; sidx++) {
+ neighbor = utf_ptr2char(p);
+ MB_PTR_ADV(p);
+ }
+ const int curr = utf_ptr2char(p);
+
+ if (mb_islower(neighbor) && mb_isupper(curr)) {
+ score += CAMEL_BONUS;
+ }
+
+ // Bonus if the match follows a separator character
+ if (neighbor == '/' || neighbor == '\\') {
+ score += PATH_SEPARATOR_BONUS;
+ } else if (neighbor == ' ' || neighbor == '_') {
+ score += WORD_SEPARATOR_BONUS;
+ }
+ } else {
+ // First letter
+ score += FIRST_LETTER_BONUS;
+ }
+ }
+ return score;
+}
+
+/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
+/// @return the number of matching characters.
+static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx,
+ int *const outScore, const char_u *const strBegin,
+ const int strLen, const uint32_t *const srcMatches,
+ uint32_t *const matches, const int maxMatches, int nextMatch,
+ int *const recursionCount)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Recursion params
+ bool recursiveMatch = false;
+ uint32_t bestRecursiveMatches[MAX_FUZZY_MATCHES];
+ int bestRecursiveScore = 0;
+
+ // Count recursions
+ (*recursionCount)++;
+ if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) {
+ return 0;
+ }
+
+ // Detect end of strings
+ if (*fuzpat == NUL || *str == NUL) {
+ return 0;
+ }
+
+ // Loop through fuzpat and str looking for a match
+ bool first_match = true;
+ while (*fuzpat != NUL && *str != NUL) {
+ const int c1 = utf_ptr2char(fuzpat);
+ const int c2 = utf_ptr2char(str);
+
+ // Found match
+ if (mb_tolower(c1) == mb_tolower(c2)) {
+ // Supplied matches buffer was too short
+ if (nextMatch >= maxMatches) {
+ return 0;
+ }
+
+ // "Copy-on-Write" srcMatches into matches
+ if (first_match && srcMatches != NULL) {
+ memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
+ first_match = false;
+ }
+
+ // Recursive call that "skips" this match
+ uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
+ int recursiveScore = 0;
+ const char_u *const next_char = str + utfc_ptr2len(str);
+ if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen,
+ matches, recursiveMatches,
+ sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch,
+ recursionCount)) {
+ // Pick best recursive score
+ if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
+ memcpy(bestRecursiveMatches, recursiveMatches,
+ MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
+ bestRecursiveScore = recursiveScore;
+ }
+ recursiveMatch = true;
+ }
+
+ // Advance
+ matches[nextMatch++] = strIdx;
+ MB_PTR_ADV(fuzpat);
+ }
+ MB_PTR_ADV(str);
+ strIdx++;
+ }
+
+ // Determine if full fuzpat was matched
+ const bool matched = *fuzpat == NUL;
+
+ // Calculate score
+ if (matched) {
+ *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch);
+ }
+
+ // Return best result
+ if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) {
+ // Recursive score is better than "this"
+ memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
+ *outScore = bestRecursiveScore;
+ return nextMatch;
+ } else if (matched) {
+ return nextMatch; // "this" score is better than recursive
+ }
+
+ return 0; // no match
+}
+
+/// fuzzy_match()
+///
+/// Performs exhaustive search via recursion to find all possible matches and
+/// match with highest score.
+/// Scores values have no intrinsic meaning. Possible score range is not
+/// normalized and varies with pattern.
+/// Recursion is limited internally (default=10) to prevent degenerate cases
+/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
+/// Uses char_u for match indices. Therefore patterns are limited to
+/// MAX_FUZZY_MATCHES characters.
+///
+/// @return true if 'pat_arg' matches 'str'. Also returns the match score in
+/// 'outScore' and the matching character positions in 'matches'.
+bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq,
+ int *const outScore, uint32_t *const matches, const int maxMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const int len = mb_charlen(str);
+ bool complete = false;
+ int numMatches = 0;
+
+ *outScore = 0;
+
+ char_u *const save_pat = vim_strsave(pat_arg);
+ char_u *pat = save_pat;
+ char_u *p = pat;
+
+ // Try matching each word in 'pat_arg' in 'str'
+ while (true) {
+ if (matchseq) {
+ complete = true;
+ } else {
+ // Extract one word from the pattern (separated by space)
+ p = skipwhite(p);
+ if (*p == NUL) {
+ break;
+ }
+ pat = p;
+ while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) {
+ MB_PTR_ADV(p);
+ }
+ if (*p == NUL) { // processed all the words
+ complete = true;
+ }
+ *p = NUL;
+ }
+
+ int score = 0;
+ int recursionCount = 0;
+ const int matchCount
+ = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches,
+ maxMatches - numMatches, 0, &recursionCount);
+ if (matchCount == 0) {
+ numMatches = 0;
+ break;
+ }
+
+ // Accumulate the match score and the number of matches
+ *outScore += score;
+ numMatches += matchCount;
+
+ if (complete) {
+ break;
+ }
+
+ // try matching the next word
+ p++;
+ }
+
+ xfree(save_pat);
+ return numMatches != 0;
+}
+
+/// Sort the fuzzy matches in the descending order of the match score.
+/// For items with same score, retain the order using the index (stable sort)
+static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ const int v1 = ((const fuzzyItem_T *)s1)->score;
+ const int v2 = ((const fuzzyItem_T *)s2)->score;
+ const int idx1 = ((const fuzzyItem_T *)s1)->idx;
+ const int idx2 = ((const fuzzyItem_T *)s2)->idx;
+
+ return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+}
+
+/// Fuzzy search the string 'str' in a list of 'items' and return the matching
+/// strings in 'fmatchlist'.
+/// If 'matchseq' is true, then for multi-word search strings, match all the
+/// words in sequence.
+/// If 'items' is a list of strings, then search for 'str' in the list.
+/// If 'items' is a list of dicts, then either use 'key' to lookup the string
+/// for each item or use 'item_cb' Funcref function to get the string.
+/// If 'retmatchpos' is true, then return a list of positions where 'str'
+/// matches for each item.
+static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq,
+ const char_u *const key, Callback *const item_cb,
+ const bool retmatchpos, list_T *const fmatchlist)
+ FUNC_ATTR_NONNULL_ARG(2, 5, 7)
+{
+ const long len = tv_list_len(items);
+ if (len == 0) {
+ return;
+ }
+
+ fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T));
+ long i = 0;
+ bool found_match = false;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+
+ // For all the string items in items, get the fuzzy matching score
+ TV_LIST_ITER(items, li, {
+ ptrs[i].idx = i;
+ ptrs[i].item = li;
+ ptrs[i].score = SCORE_NONE;
+ char_u *itemstr = NULL;
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN;
+ const typval_T *const tv = TV_LIST_ITEM_TV(li);
+ if (tv->v_type == VAR_STRING) { // list of strings
+ itemstr = tv->vval.v_string;
+ } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) {
+ // For a dict, either use the specified key to lookup the string or
+ // use the specified callback function to get the string.
+ if (key != NULL) {
+ itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false);
+ } else {
+ typval_T argv[2];
+
+ // Invoke the supplied callback (if any) to get the dict item
+ tv->vval.v_dict->dv_refcount++;
+ argv[0].v_type = VAR_DICT;
+ argv[0].vval.v_dict = tv->vval.v_dict;
+ argv[1].v_type = VAR_UNKNOWN;
+ if (callback_call(item_cb, 1, argv, &rettv)) {
+ if (rettv.v_type == VAR_STRING) {
+ itemstr = rettv.vval.v_string;
+ }
+ }
+ tv_dict_unref(tv->vval.v_dict);
+ }
+ }
+
+ int score;
+ if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches,
+ sizeof(matches) / sizeof(matches[0]))) {
+ // Copy the list of matching positions in itemstr to a list, if
+ // 'retmatchpos' is set.
+ if (retmatchpos) {
+ ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow);
+ int j = 0;
+ const char_u *p = str;
+ while (*p != NUL) {
+ if (!ascii_iswhite(utf_ptr2char(p))) {
+ tv_list_append_number(ptrs[i].lmatchpos, matches[j]);
+ j++;
+ }
+ MB_PTR_ADV(p);
+ }
+ }
+ ptrs[i].score = score;
+ found_match = true;
+ }
+ i++;
+ tv_clear(&rettv);
+ });
+
+ if (found_match) {
+ // Sort the list by the descending order of the match score
+ qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare);
+
+ // For matchfuzzy(), return a list of matched strings.
+ // ['str1', 'str2', 'str3']
+ // For matchfuzzypos(), return a list with three items.
+ // The first item is a list of matched strings. The second item
+ // is a list of lists where each list item is a list of matched
+ // character positions. The third item is a list of matching scores.
+ // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
+ list_T *l;
+ if (retmatchpos) {
+ const listitem_T *const li = tv_list_find(fmatchlist, 0);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ } else {
+ l = fmatchlist;
+ }
+
+ // Copy the matching strings with a valid score to the return list
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item));
+ }
+
+ // next copy the list of matching positions
+ if (retmatchpos) {
+ const listitem_T *li = tv_list_find(fmatchlist, -2);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_list(l, ptrs[i].lmatchpos);
+ }
+
+ // copy the matching scores
+ li = tv_list_find(fmatchlist, -1);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_number(l, ptrs[i].score);
+ }
+ }
+ }
+ xfree(ptrs);
+}
+
+/// Do fuzzy matching. Returns the list of matched strings in 'rettv'.
+/// If 'retmatchpos' is true, also returns the matching character positions.
+static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
+ const bool retmatchpos)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // validate and get the arguments
+ if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) {
+ semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
+ return;
+ }
+ if (argvars[1].v_type != VAR_STRING || argvars[1].vval.v_string == NULL) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[1]));
+ return;
+ }
+
+ Callback cb = CALLBACK_NONE;
+ const char_u *key = NULL;
+ bool matchseq = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+ // To search a dict, either a callback function or a key can be
+ // specified.
+ dict_T *const d = argvars[2].vval.v_dict;
+ const dictitem_T *const di = tv_dict_find(d, "key", -1);
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL
+ || *di->di_tv.vval.v_string == NUL) {
+ semsg(_(e_invarg2), tv_get_string(&di->di_tv));
+ return;
+ }
+ key = (const char_u *)tv_get_string(&di->di_tv);
+ } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) {
+ semsg(_(e_invargval), "text_cb");
+ return;
+ }
+ if (tv_dict_find(d, "matchseq", -1) != NULL) {
+ matchseq = true;
+ }
+ }
+
+ // get the fuzzy matches
+ tv_list_alloc_ret(rettv, retmatchpos ? 3 : kListLenUnknown);
+ if (retmatchpos) {
+ // For matchfuzzypos(), a list with three items are returned. First
+ // item is a list of matching strings, the second item is a list of
+ // lists with matching positions within each string and the third item
+ // is the list of scores of the matches.
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ }
+
+ fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key,
+ &cb, retmatchpos, rettv->vval.v_list);
+ callback_free(&cb);
+}
+
+/// "matchfuzzy()" function
+void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, false);
+}
+
+/// "matchfuzzypos()" function
+void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, true);
+}
+
/// Find identifiers or defines in included files.
/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
///
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 15b8d41f39..53059cc1ea 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -55,6 +55,9 @@
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
+/// Maximum number of characters that can be fuzzy matched
+#define MAX_FUZZY_MATCHES 256
+
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index f6b95f37b1..d7b220b3f6 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -5654,7 +5654,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo
// If the .add file is edited somewhere, reload it.
if (buf != NULL) {
- buf_reload(buf, buf->b_orig_mode);
+ buf_reload(buf, buf->b_orig_mode, false);
}
redraw_all_later(SOME_VALID);
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 3aef654a8e..2b74c45478 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -3112,9 +3112,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_conceal) {
- msg(_("syntax conceal on"));
+ msg("syntax conceal on");
} else {
- msg(_("syntax conceal off"));
+ msg("syntax conceal off");
}
} else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
curwin->w_s->b_syn_conceal = true;
@@ -3141,9 +3141,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_ic) {
- msg(_("syntax case ignore"));
+ msg("syntax case ignore");
} else {
- msg(_("syntax case match"));
+ msg("syntax case match");
}
} else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
curwin->w_s->b_syn_ic = false;
@@ -3168,9 +3168,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
if (*arg == NUL) {
switch (curwin->w_s->b_syn_foldlevel) {
case SYNFLD_START:
- msg(_("syntax foldlevel start")); break;
+ msg("syntax foldlevel start"); break;
case SYNFLD_MINIMUM:
- msg(_("syntax foldlevel minimum")); break;
+ msg("syntax foldlevel minimum"); break;
default:
break;
}
@@ -3209,11 +3209,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
- msg(_("syntax spell toplevel"));
+ msg("syntax spell toplevel");
} else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
- msg(_("syntax spell notoplevel"));
+ msg("syntax spell notoplevel");
} else {
- msg(_("syntax spell default"));
+ msg("syntax spell default");
}
} else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
curwin->w_s->b_syn_spell = SYNSPL_TOP;
@@ -3245,7 +3245,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
if (*arg == NUL) {
msg_puts("\n");
if (curwin->w_s->b_syn_isk != empty_option) {
- msg_puts(_("syntax iskeyword "));
+ msg_puts("syntax iskeyword ");
msg_outtrans(curwin->w_s->b_syn_isk);
} else {
msg_outtrans((char_u *)_("syntax iskeyword not set"));
@@ -6758,16 +6758,26 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
{ NULL, -1, NIL },
};
+ char hex_name[8];
+ char *name;
+
for (int j = 0; cattrs[j].dest; j++) {
- if (cattrs[j].val != -1) {
+ if (cattrs[j].val < 0) {
+ XFREE_CLEAR(*cattrs[j].dest);
+ continue;
+ }
+
+ if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
+ name = cattrs[j].name.data.string.data;
+ } else {
+ snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val);
+ name = hex_name;
+ }
+
+ if (!*cattrs[j].dest
+ || STRCMP(*cattrs[j].dest, name) != 0) {
xfree(*cattrs[j].dest);
- if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
- *cattrs[j].dest = xstrdup(cattrs[j].name.data.string.data);
- } else {
- char hex_name[8];
- snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val);
- *cattrs[j].dest = xstrdup(hex_name);
- }
+ *cattrs[j].dest = xstrdup(name);
}
}
@@ -8849,6 +8859,22 @@ RgbValue name_to_color(const char *name)
return -1;
}
+int name_to_ctermcolor(const char *name)
+{
+ int i;
+ int off = TOUPPER_ASC(*name);
+ for (i = ARRAY_SIZE(color_names); --i >= 0;) {
+ if (off == color_names[i][0]
+ && STRICMP(name+1, color_names[i]+1) == 0) {
+ break;
+ }
+ }
+ if (i < 0) {
+ return -1;
+ }
+ TriState bold = kNone;
+ return lookup_color(i, false, &bold);
+}
/**************************************
* End of Highlighting stuff *
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 54d7e54fb4..32d72218c8 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2960,7 +2960,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help)
} else {
RedrawingDisabled--;
if (postponed_split) { // close the window
- win_close(curwin, false);
+ win_close(curwin, false, false);
postponed_split = 0;
}
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a2d855244c..1c26e46a21 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -172,6 +172,11 @@ void terminal_teardown(void)
pmap_init(ptr_t, &invalidated_terminals);
}
+static void term_output_callback(const char *s, size_t len, void *user_data)
+{
+ terminal_send((Terminal *)user_data, (char *)s, len);
+}
+
// public API {{{
Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
@@ -195,6 +200,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
vterm_screen_reset(rv->vts, 1);
+ vterm_output_set_callback(rv->vt, term_output_callback, rv);
// force a initial refresh of the screen to ensure the buffer will always
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
@@ -636,7 +642,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
return;
}
vterm_keyboard_start_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
size_t buff_len = STRLEN(y_array[0]);
char_u *buff = xmalloc(buff_len);
for (int i = 0; i < count; i++) { // -V756
@@ -667,14 +672,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
}
xfree(buff);
vterm_keyboard_end_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
-}
-
-void terminal_flush_output(Terminal *term)
-{
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
}
void terminal_send_key(Terminal *term, int c)
@@ -693,8 +690,6 @@ void terminal_send_key(Terminal *term, int c)
} else {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
-
- terminal_flush_output(term);
}
void terminal_receive(Terminal *term, char *data, size_t len)
@@ -1265,9 +1260,6 @@ static bool send_mouse_event(Terminal *term, int c)
}
mouse_action(term, button, row, col - offset, pressed, 0);
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
return false;
}
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index fdae0697c3..15e3b31498 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -10,6 +10,7 @@ let s:did_load = 1
set backspace=
set directory^=.
set fillchars=vert:\|,fold:-
+set fsync
set laststatus=1
set listchars=eol:$
set joinspaces
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index 52f243aaea..28c5948142 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -1,5 +1,55 @@
" Test that the methods used for testing work.
+func Test_assert_false()
+ call assert_equal(0, assert_false(0))
+ call assert_equal(0, assert_false(v:false))
+ call assert_equal(0, v:false->assert_false())
+
+ call assert_equal(1, assert_false(123))
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 123->assert_false())
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_true()
+ call assert_equal(0, assert_true(1))
+ call assert_equal(0, assert_true(123))
+ call assert_equal(0, assert_true(v:true))
+ call assert_equal(0, v:true->assert_true())
+
+ call assert_equal(1, assert_true(0))
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 0->assert_true())
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_equal()
+ let s = 'foo'
+ call assert_equal(0, assert_equal('foo', s))
+ let n = 4
+ call assert_equal(0, assert_equal(4, n))
+ let l = [1, 2, 3]
+ call assert_equal(0, assert_equal([1, 2, 3], l))
+ call assert_equal(v:_null_list, v:_null_list)
+ call assert_equal(v:_null_list, [])
+ call assert_equal([], v:_null_list)
+
+ let s = 'foo'
+ call assert_equal(1, assert_equal('bar', s))
+ call assert_match("Expected 'bar' but got 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+
+ 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)
+endfunc
+
func Test_assert_equalfile()
call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
call assert_match("E485: Can't read file abcabc", v:errors[0])
@@ -46,17 +96,129 @@ func Test_assert_equalfile()
call delete('Xtwo')
endfunc
+func Test_assert_notequal()
+ let n = 4
+ call assert_equal(0, assert_notequal('foo', n))
+ let s = 'foo'
+ call assert_equal(0, assert_notequal([1, 2, 3], s))
+
+ call assert_equal(1, assert_notequal('foo', s))
+ call assert_match("Expected not equal to 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_report()
+ call assert_equal(1, assert_report('something is wrong'))
+ call assert_match('something is wrong', v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, 'also wrong'->assert_report())
+ call assert_match('also wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_exception()
+ try
+ nocommand
+ catch
+ call assert_equal(0, assert_exception('E492:'))
+ endtry
+
+ try
+ nocommand
+ catch
+ try
+ " illegal argument, get NULL for error
+ call assert_equal(1, assert_exception([]))
+ catch
+ call assert_equal(0, assert_exception('E730:'))
+ endtry
+ endtry
+endfunc
+
+func Test_wrong_error_type()
+ let save_verrors = v:errors
+ let v:['errors'] = {'foo': 3}
+ call assert_equal('yes', 'no')
+ let verrors = v:errors
+ let v:errors = save_verrors
+ call assert_equal(type([]), type(verrors))
+endfunc
+
+func Test_match()
+ call assert_equal(0, assert_match('^f.*b.*r$', 'foobar'))
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar'))
+ call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_notmatch()
+ call assert_equal(0, assert_notmatch('foo', 'bar'))
+ call assert_equal(0, assert_notmatch('^foobar$', 'foobars'))
+
+ call assert_equal(1, assert_notmatch('foo', 'foobar'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_notmatch('foo'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_fail_fails()
+ call assert_equal(1, assert_fails('xxx', 'E12345'))
+ call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid'))
+ call assert_match("stupid: Expected '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)
+
+ 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)
+endfunc
+
func Test_assert_fails_in_try_block()
try
call assert_equal(0, assert_fails('throw "error"'))
endtry
endfunc
+func Test_assert_beeps()
+ new
+ call assert_equal(0, assert_beeps('normal h'))
+
+ call assert_equal(1, assert_beeps('normal 0'))
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(0, 'normal h'->assert_beeps())
+ call assert_equal(1, 'normal 0'->assert_beeps())
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ bwipe
+endfunc
+
func Test_assert_inrange()
call assert_equal(0, assert_inrange(7, 7, 7))
call assert_equal(0, assert_inrange(5, 7, 5))
call assert_equal(0, assert_inrange(5, 7, 6))
call assert_equal(0, assert_inrange(5, 7, 7))
+
call assert_equal(1, assert_inrange(5, 7, 4))
call assert_match("Expected range 5 - 7, but got 4", v:errors[0])
call remove(v:errors, 0)
@@ -64,6 +226,12 @@ func Test_assert_inrange()
call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
call remove(v:errors, 0)
+ call assert_equal(0, 5->assert_inrange(5, 7))
+ call assert_equal(0, 7->assert_inrange(5, 7))
+ call assert_equal(1, 8->assert_inrange(5, 7))
+ call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+
call assert_fails('call assert_inrange(1, 1)', 'E119:')
if has('float')
@@ -83,6 +251,12 @@ func Test_assert_inrange()
endif
endfunc
+func Test_assert_with_msg()
+ call assert_equal('foo', 'bar', 'testing')
+ call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
" Must be last.
func Test_zz_quit_detected()
" Verify that if a test function ends Vim the test script detects this.
diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim
index 9ad727241e..53ed4617f7 100644
--- a/src/nvim/testdir/test_autochdir.vim
+++ b/src/nvim/testdir/test_autochdir.vim
@@ -64,4 +64,14 @@ func Test_verbose_pwd()
call delete('Xautodir', 'rf')
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:')
+ set nowic
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index a8d51ef598..c39546b9ea 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -502,7 +502,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
[CODE]
call writefile(content, 'Xvimrc')
- call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
+ call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
call assert_match('E814', errors)
@@ -562,7 +562,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost2()
[CODE]
call writefile(content, 'Xvimrc')
- call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
+ call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
" This probably only ever matches on unix.
call assert_notmatch('Caught deadly signal SEGV', errors)
@@ -1506,7 +1506,7 @@ func Test_bufunload_all()
call writefile(content, 'Xtest')
call delete('Xout')
- call system(v:progpath. ' -u NORC -i NONE -N -S Xtest')
+ call system(GetVimCommandClean() .. ' -N --headless -S Xtest')
call assert_true(filereadable('Xout'))
call delete('Xxx1')
@@ -1827,6 +1827,14 @@ func Test_autocommand_all_events()
call assert_fails('au * x bwipe', 'E1155:')
endfunc
+func Test_autocmd_user()
+ au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")]
+ doautocmd User MyEvent
+ call assert_equal(['MyEvent', 'MyEvent'], s:res)
+ au! User
+ unlet s:res
+endfunc
+
function s:Before_test_dirchanged()
augroup test_dirchanged
autocmd!
@@ -1850,14 +1858,16 @@ endfunc
function Test_dirchanged_global()
call s:Before_test_dirchanged()
+ autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo]
+ call assert_equal(expected, s:li)
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
call s:After_test_dirchanged()
endfunc
@@ -1879,6 +1889,7 @@ function Test_dirchanged_auto()
CheckOption autochdir
call s:Before_test_dirchanged()
call test_autochdir()
+ autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
set acd
@@ -1886,7 +1897,8 @@ function Test_dirchanged_auto()
call assert_equal([], s:li)
exe 'edit ' . s:dir_foo . '/Xfile'
call assert_equal(s:dir_foo, getcwd())
- call assert_equal(["auto:", s:dir_foo], s:li)
+ let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
+ call assert_equal(expected, s:li)
set noacd
bwipe!
call s:After_test_dirchanged()
diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim
index 38978ef689..7b56b1554f 100644
--- a/src/nvim/testdir/test_blockedit.vim
+++ b/src/nvim/testdir/test_blockedit.vim
@@ -81,4 +81,52 @@ func Test_blockinsert_delete()
bwipe!
endfunc
+func Test_blockappend_eol_cursor()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>x\<esc>"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>"
+ call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_blockappend_eol_cursor2()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>x\<esc>"
+ call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>"
+ call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (to the beginning of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 4 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 5 Move 4 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$'))
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 8d592f21ea..438edb0257 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -1,7 +1,7 @@
" Test for breakindent
"
" Note: if you get strange failures when adding new tests, it might be that
-" while the test is run, the breakindent cacheing gets in its way.
+" 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')
@@ -20,7 +20,7 @@ func s:screen_lines2(lnums, lnume, width) abort
return ScreenLines([a:lnums, a:lnume], a:width)
endfunc
-func! s:compare_lines(expect, actual)
+func s:compare_lines(expect, actual)
call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
endfunc
diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim
new file mode 100644
index 0000000000..aa2e4f1a8c
--- /dev/null
+++ b/src/nvim/testdir/test_cdo.vim
@@ -0,0 +1,205 @@
+" Tests for the :cdo, :cfdo, :ldo and :lfdo commands
+
+if !has('quickfix')
+ throw 'Skipped: quickfix feature missing'
+endif
+
+" Create the files used by the tests
+function SetUp()
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
+endfunction
+
+" Remove the files used by the tests
+function TearDown()
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+ call delete('Xtestfile3')
+endfunction
+
+" Returns the current line in '<filename> <linenum>L <column>C' format
+function GetRuler()
+ return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
+endfunction
+
+" Tests for the :cdo and :ldo commands
+function XdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cdo and ldo commands
+ let Xdo = a:cchar . 'do'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let Xprev = a:cchar. 'prev'
+ let XdoCmd = Xdo . ' call add(l, GetRuler())'
+
+ " Try with an empty list
+ let l = []
+ exe XdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ enew
+ exe "2,3" . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Boundary condition tests
+ let l = []
+ enew
+ exe "1,1" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C'], l)
+
+ let l = []
+ enew
+ exe "3" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Range test commands
+ let l = []
+ enew
+ exe "%" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe "1,$" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe Xprev
+ exe "." . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ let l = []
+ enew
+ exe "+" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Invalid error lines test
+ let l = []
+ enew
+ exe "silent! 27" . XdoCmd
+ exe "silent! 4,5" . XdoCmd
+ call assert_equal([], l)
+
+ " Run commands from an unsaved buffer
+ let v:errmsg=''
+ let l = []
+ enew
+ setlocal modified
+ exe "silent! 2,2" . XdoCmd
+ if v:errmsg !~# 'No write since last change'
+ call add(v:errors, 'Unsaved file change test failed')
+ endif
+
+ " If the executed command fails, then the operation should be aborted
+ enew!
+ let subst_count = 0
+ exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
+ if subst_count != 1 || getline('.') != 'xLine1'
+ call add(v:errors, 'Abort command on error test failed')
+ endif
+
+ let l = []
+ exe "2,2" . Xdo . "! call add(l, GetRuler())"
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with no valid error entries
+ let l = []
+ edit! +2 Xtestfile1
+ exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
+ exe XdoCmd
+ call assert_equal([], l)
+ exe "silent! 2" . XdoCmd
+ call assert_equal([], l)
+ let v:errmsg=''
+ exe "%" . XdoCmd
+ exe "1,$" . XdoCmd
+ exe "." . XdoCmd
+ call assert_equal('', v:errmsg)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
+ exe XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+endfunction
+
+" Tests for the :cfdo and :lfdo commands
+function XfdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cfdo and lfdo commands
+ let Xfdo = a:cchar . 'fdo'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
+ let Xpfile = a:cchar. 'pfile'
+
+ " Clear the quickfix/location list
+ exe Xgetexpr . " []"
+
+ " Try with an empty list
+ let l = []
+ exe XfdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ exe "2,3" . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Boundary condition tests
+ let l = []
+ exe "3" . XfdoCmd
+ call assert_equal(['Xtestfile3 2L 3C'], l)
+
+ " Range test commands
+ let l = []
+ exe "%" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe "1,$" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe Xpfile
+ exe "." . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
+ exe XfdoCmd
+ call assert_equal(['Xtestfile2 2L 5C'], l)
+
+endfunction
+
+" Tests for cdo and cfdo
+function Test_cdo()
+ call XdoTests('c')
+ call XfdoTests('c')
+endfunction
+
+" Tests for ldo and lfdo
+function Test_ldo()
+ call XdoTests('l')
+ call XfdoTests('l')
+endfunction
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 5dc54111e7..4b702bf2b8 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -815,7 +815,7 @@ func Test_cindent_1()
}
}
- public: // <-- this was incoreectly indented before!!
+ public: // <-- this was incorrectly indented before!!
void testfall();
protected:
void testfall();
@@ -1792,7 +1792,7 @@ func Test_cindent_1()
}
}
- public: // <-- this was incoreectly indented before!!
+ public: // <-- this was incorrectly indented before!!
void testfall();
protected:
void testfall();
@@ -5302,9 +5302,12 @@ endfunc
" this was going beyond the end of the line.
func Test_cindent_case()
new
- call setline(1, "case x: // x")
+ call setline(1, 'case x: // x')
set cindent
norm! f:a:
+ call assert_equal('case x:: // x', getline(1))
+
+ set cindent&
bwipe!
endfunc
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index f2ffd50726..57825b4551 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -75,7 +75,6 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
- throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
@@ -103,9 +102,10 @@ func Test_screenpos()
bwipe!
call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nmenu WinBar.TEST :
- call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nunmenu WinBar.TEST
+ " Needs WinBar
+ " nmenu WinBar.TEST :
+ " call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
+ " nunmenu WinBar.TEST
endfunc
func Test_screenpos_number()
@@ -123,11 +123,18 @@ func Test_screenpos_number()
bwipe!
endfunc
+" Save the visual start character position
func SaveVisualStartCharPos()
call add(g:VisualStartPos, getcharpos('v'))
return ''
endfunc
+" Save the current cursor character position in insert mode
+func SaveInsertCurrentCharPos()
+ call add(g:InsertCurrentPos, getcharpos('.'))
+ return ''
+endfunc
+
" Test for the getcharpos() function
func Test_getcharpos()
call assert_fails('call getcharpos({})', 'E731:')
@@ -156,16 +163,29 @@ func Test_getcharpos()
vnoremap <expr> <F3> SaveVisualStartCharPos()
let g:VisualStartPos = []
exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
- call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos)
+ call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos)
call assert_equal([0, 2, 9, 0], getcharpos('v'))
let g:VisualStartPos = []
exe "normal 3Gv$\<F3>o\<F3>"
- call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos)
+ call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos)
let g:VisualStartPos = []
exe "normal 1Gv$\<F3>o\<F3>"
call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
vunmap <F3>
+ " Test for getting the position in insert mode with the cursor after the
+ " last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharPos()
+ let g:InsertCurrentPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0],
+ \ [0, 2, 7, 0]], g:InsertCurrentPos)
+ iunmap <F3>
+
%bw!
endfunc
@@ -192,6 +212,10 @@ func Test_setcharpos()
call setcharpos("'m", [0, 2, 9, 0])
normal `m
call assert_equal([2, 11], [line('.'), col('.')])
+ " unload the buffer and try to set the mark
+ let bnr = bufnr()
+ enew!
+ call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0]))
%bw!
call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
@@ -202,6 +226,11 @@ func SaveVisualStartCharCol()
return ''
endfunc
+func SaveInsertCurrentCharCol()
+ call add(g:InsertCurrentCol, charcol('.'))
+ return ''
+endfunc
+
" Test for the charcol() function
func Test_charcol()
call assert_fails('call charcol({})', 'E731:')
@@ -239,19 +268,36 @@ func Test_charcol()
vnoremap <expr> <F3> SaveVisualStartCharCol()
let g:VisualStartCol = []
exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
- call assert_equal([7, 9, 5], g:VisualStartCol)
+ call assert_equal([7, 10, 5], g:VisualStartCol)
call assert_equal(9, charcol('v'))
let g:VisualStartCol = []
exe "normal 3Gv$\<F3>o\<F3>"
- call assert_equal([1, 1], g:VisualStartCol)
+ call assert_equal([1, 2], g:VisualStartCol)
let g:VisualStartCol = []
exe "normal 1Gv$\<F3>o\<F3>"
call assert_equal([1, 1], g:VisualStartCol)
vunmap <F3>
+ " Test for getting the column number in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharCol()
+ let g:InsertCurrentCol = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol)
+ iunmap <F3>
+
%bw!
endfunc
+func SaveInsertCursorCharPos()
+ call add(g:InsertCursorPos, getcursorcharpos('.'))
+ return ''
+endfunc
+
" Test for getcursorcharpos()
func Test_getcursorcharpos()
call assert_equal(getcursorcharpos(), getcursorcharpos(0))
@@ -269,6 +315,19 @@ func Test_getcursorcharpos()
normal 4G9l
call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
+ " Test for getting the cursor position in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCursorCharPos()
+ let g:InsertCursorPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2],
+ \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos)
+ iunmap <F3>
+
let winid = win_getid()
normal 2G5l
wincmd w
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index d23748a3e3..5965ee48ef 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -81,7 +81,7 @@ func Test_digraphs()
call Put_Dig(".e")
call Put_Dig("a.") " not defined
call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaresis
+ " Diaeresis
call Put_Dig("a:")
call Put_Dig(":u")
call Put_Dig("b:") " not defined
@@ -288,7 +288,7 @@ func Test_digraphs_option()
call Put_Dig_BS(".","e")
call Put_Dig_BS("a",".") " not defined
call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaresis
+ " Diaeresis
call Put_Dig_BS("a",":")
call Put_Dig_BS(":","u")
call Put_Dig_BS("b",":") " not defined
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index a1f6a84a99..360b3aaaa0 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -590,7 +590,7 @@ func Test_edit_CTRL_K()
call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
call assert_equal(['AA'], getline(1, '$'))
- " press an unexecpted key after dictionary completion
+ " press an unexpected key after dictionary completion
%d
call setline(1, 'A')
call cursor(1, 1)
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
index 78663f7deb..dcec5f7cc6 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -29,12 +29,11 @@ endfunc
" Test editing line in Ex mode (both Q and gQ)
func Test_ex_mode()
- throw 'skipped: TODO: '
+ throw 'Skipped: Nvim only supports Vim Ex mode'
let encoding_save = &encoding
set sw=2
- " for e in ['utf8', 'latin1']
- for e in ['utf8']
+ for e in ['utf8', 'latin1']
exe 'set encoding=' . e
call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index bbf8b4dfc8..8055a51a11 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -409,4 +409,18 @@ func Test_not_break_expression_register()
call assert_equal('1+1', getreg('=', 1))
endfunc
+func Test_address_line_overflow()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+ new
+ call setline(1, 'text')
+ call assert_fails('|.44444444444444444444444', 'E1247:')
+ call assert_fails('|.9223372036854775806', 'E1247:')
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index b95cd5faf8..06ccd6e85f 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -1,9 +1,10 @@
" Tests for when a file was changed outside of Vim.
+source check.vim
+
func Test_FileChangedShell_reload()
- if !has('unix')
- return
- endif
+ CheckUnix
+
augroup testreload
au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
augroup END
@@ -90,11 +91,107 @@ func Test_FileChangedShell_reload()
call delete('Xchanged_r')
endfunc
+func Test_FileChangedShell_edit()
+ CheckUnix
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only, no 'ff' etc)
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload with 'ff', etc
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal('line1', getline(1))
+ call assert_equal('line2', getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+endfunc
+
+func Test_FileChangedShell_edit_dialog()
+ throw 'Skipped: requires a UI to be active'
+ CheckNotGui
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call feedkeys('L', 'L') " load file content only
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload (file and options) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call feedkeys('a', 'L') " load file content and options
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal("line1", getline(1))
+ call assert_equal("line2", getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+endfunc
+
func Test_file_changed_dialog()
- throw 'skipped: TODO: '
- if !has('unix') || has('gui_running')
- return
- endif
+ throw 'Skipped: requires a UI to be active'
+ CheckUnix
+ CheckNotGui
au! FileChangedShell
new Xchanged_d
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 4ef35b3a46..c2cfac6fe3 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -81,6 +81,7 @@ let s:filename_checks = {
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
+ \ 'bicep': ['file.bicep'],
\ 'beancount': ['file.beancount'],
\ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'],
\ 'blank': ['file.bl'],
@@ -271,6 +272,7 @@ let s:filename_checks = {
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
\ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
+ \ 'javascript.glimmer': ['file.gjs'],
\ 'javascriptreact': ['file.jsx'],
\ 'jess': ['file.clp'],
\ 'jgraph': ['file.jgr'],
@@ -435,6 +437,7 @@ let s:filename_checks = {
\ 'readline': ['.inputrc', 'inputrc'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
\ 'rego': ['file.rego'],
+ \ 'rescript': ['file.res', 'file.resi'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
\ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
@@ -476,6 +479,7 @@ let s:filename_checks = {
\ 'skill': ['file.il', 'file.ils', 'file.cdf'],
\ 'slang': ['file.sl'],
\ 'slice': ['file.ice'],
+ \ 'solidity': ['file.sol'],
\ 'solution': ['file.sln'],
\ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
\ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'],
@@ -543,6 +547,7 @@ let s:filename_checks = {
\ 'tssgm': ['file.tssgm'],
\ 'tssop': ['file.tssop'],
\ 'twig': ['file.twig'],
+ \ 'typescript.glimmer': ['file.gts'],
\ 'typescriptreact': ['file.tsx'],
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
index 0c45db049b..d465e48c7b 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/src/nvim/testdir/test_filter_cmd.vim
@@ -145,3 +145,38 @@ func Test_filter_commands()
bwipe! file.h
bwipe! file.hs
endfunc
+
+func Test_filter_display()
+ edit Xdoesnotmatch
+ let @a = '!!willmatch'
+ let @b = '!!doesnotmatch'
+ let @c = "oneline\ntwoline\nwillmatch\n"
+ let @/ = '!!doesnotmatch'
+ call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
+ let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
+
+ call assert_true(index(lines, '"a') >= 0)
+ call assert_false(index(lines, '"b') >= 0)
+ call assert_true(index(lines, '"c') >= 0)
+ call assert_false(index(lines, '"/') >= 0)
+ call assert_false(index(lines, '":') >= 0)
+ call assert_false(index(lines, '"%') >= 0)
+
+ let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
+ call assert_true(index(lines, '"a') < 0)
+ call assert_false(index(lines, '"b') < 0)
+ call assert_true(index(lines, '"c') < 0)
+ call assert_false(index(lines, '"/') < 0)
+ call assert_false(index(lines, '":') < 0)
+ call assert_false(index(lines, '"%') < 0)
+
+ bwipe!
+endfunc
+
+func Test_filter_scriptnames()
+ let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
+ call assert_equal(1, len(lines))
+ call assert_match('filter_cmd', lines[0])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 438bed51c6..994d74601a 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -332,37 +332,6 @@ func Test_simplify()
call assert_fails('call simplify(1.2)', 'E806:')
endfunc
-func Test_setbufvar_options()
- " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
- " window layout.
- call assert_equal(1, winnr('$'))
- split dummy_preview
- resize 2
- set winfixheight winfixwidth
- let prev_id = win_getid()
-
- wincmd j
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf1', v:true)
- call setbufvar(dummy_buf, '&buftype', 'nofile')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
- let dum1_id = win_getid()
-
- wincmd h
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf2', v:true)
- eval 'nofile'->setbufvar(dummy_buf, '&buftype')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
-
- bwipe!
- call win_gotoid(prev_id)
- bwipe!
- call win_gotoid(dum1_id)
- bwipe!
-endfunc
-
func Test_pathshorten()
call assert_equal('', pathshorten(''))
call assert_equal('foo', pathshorten('foo'))
@@ -1293,6 +1262,37 @@ func Test_shellescape()
let &shell = save_shell
endfunc
+func Test_setbufvar_options()
+ " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
+ " window layout.
+ call assert_equal(1, winnr('$'))
+ split dummy_preview
+ resize 2
+ set winfixheight winfixwidth
+ let prev_id = win_getid()
+
+ wincmd j
+ let wh = winheight(0)
+ let dummy_buf = bufnr('dummy_buf1', v:true)
+ call setbufvar(dummy_buf, '&buftype', 'nofile')
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+ let dum1_id = win_getid()
+
+ wincmd h
+ let wh = winheight(0)
+ let dummy_buf = bufnr('dummy_buf2', v:true)
+ eval 'nofile'->setbufvar(dummy_buf, '&buftype')
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+
+ bwipe!
+ call win_gotoid(prev_id)
+ bwipe!
+ call win_gotoid(dum1_id)
+ bwipe!
+endfunc
+
func Test_redo_in_nested_functions()
nnoremap g. :set opfunc=Operator<CR>g@
function Operator( type, ... )
@@ -1350,98 +1350,6 @@ func Test_trim()
call assert_equal("x", trim(chars . "x" . chars))
endfunc
-func EditAnotherFile()
- let word = expand('<cword>')
- edit Xfuncrange2
-endfunc
-
-func Test_func_range_with_edit()
- " Define a function that edits another buffer, then call it with a range that
- " is invalid in that buffer.
- call writefile(['just one line'], 'Xfuncrange2')
- new
- eval 10->range()->setline(1)
- write Xfuncrange1
- call assert_fails('5,8call EditAnotherFile()', 'E16:')
-
- call delete('Xfuncrange1')
- call delete('Xfuncrange2')
- bwipe!
-endfunc
-
-func Test_func_exists_on_reload()
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists')
- call assert_equal(0, exists('*ExistingFunction'))
- source Xfuncexists
- call assert_equal(1, '*ExistingFunction'->exists())
- " Redefining a function when reloading a script is OK.
- source Xfuncexists
- call assert_equal(1, exists('*ExistingFunction'))
-
- " But redefining in another script is not OK.
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2')
- call assert_fails('source Xfuncexists2', 'E122:')
-
- delfunc ExistingFunction
- call assert_equal(0, exists('*ExistingFunction'))
- call writefile([
- \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
- \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
- \ ], 'Xfuncexists')
- call assert_fails('source Xfuncexists', 'E122:')
- call assert_equal(1, exists('*ExistingFunction'))
-
- call delete('Xfuncexists2')
- call delete('Xfuncexists')
- delfunc ExistingFunction
-endfunc
-
-func Test_platform_name()
- " The system matches at most only one name.
- let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
- call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
-
- " Is Unix?
- call assert_equal(has('beos'), has('beos') && has('unix'))
- call assert_equal(has('bsd'), has('bsd') && has('unix'))
- call assert_equal(has('hpux'), has('hpux') && has('unix'))
- call assert_equal(has('linux'), has('linux') && has('unix'))
- call assert_equal(has('mac'), has('mac') && has('unix'))
- call assert_equal(has('qnx'), has('qnx') && has('unix'))
- call assert_equal(has('sun'), has('sun') && has('unix'))
- call assert_equal(has('win32'), has('win32') && !has('unix'))
- call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
-
- if has('unix') && executable('uname')
- let uname = system('uname')
- call assert_equal(uname =~? 'BeOS', has('beos'))
- " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
- call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
- call assert_equal(uname =~? 'HP-UX', has('hpux'))
- call assert_equal(uname =~? 'Linux', has('linux'))
- call assert_equal(uname =~? 'Darwin', has('mac'))
- call assert_equal(uname =~? 'QNX', has('qnx'))
- call assert_equal(uname =~? 'SunOS', has('sun'))
- call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
- endif
-endfunc
-
-sandbox function Fsandbox()
- normal ix
-endfunc
-
-func Test_func_sandbox()
- sandbox let F = {-> 'hello'}
- call assert_equal('hello', F())
-
- sandbox let F = {-> "normal ix\<Esc>"->execute()}
- call assert_fails('call F()', 'E48:')
- unlet F
-
- call assert_fails('call Fsandbox()', 'E48:')
- delfunc Fsandbox
-endfunc
-
" Test for reg_recording() and reg_executing()
func Test_reg_executing_and_recording()
let s:reg_stat = ''
@@ -1543,6 +1451,10 @@ func Test_getchar()
call assert_equal('', getcharstr(0))
call assert_equal('', getcharstr(1))
+ call feedkeys("\<M-F2>", '')
+ call assert_equal("\<M-F2>", getchar(0))
+ call assert_equal(0, getchar(0))
+
call setline(1, 'xxxx')
" call test_setmouse(1, 3)
" let v:mouse_win = 9
@@ -1615,47 +1527,96 @@ func Test_libcall_libcallnr()
call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:')
endfunc
-func Test_bufadd_bufload()
- call assert_equal(0, bufexists('someName'))
- let buf = bufadd('someName')
- call assert_notequal(0, buf)
- call assert_equal(1, bufexists('someName'))
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- call bufload(buf)
- call assert_equal(1, bufloaded(buf))
- call assert_equal([''], getbufline(buf, 1, '$'))
+sandbox function Fsandbox()
+ normal ix
+endfunc
- let curbuf = bufnr('')
- eval ['some', 'text']->writefile('XotherName')
- let buf = 'XotherName'->bufadd()
- call assert_notequal(0, buf)
- eval 'XotherName'->bufexists()->assert_equal(1)
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- eval buf->bufload()
- call assert_equal(1, bufloaded(buf))
- call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
- call assert_equal(curbuf, bufnr(''))
+func Test_func_sandbox()
+ sandbox let F = {-> 'hello'}
+ call assert_equal('hello', F())
- let buf1 = bufadd('')
- let buf2 = bufadd('')
- call assert_notequal(0, buf1)
- call assert_notequal(0, buf2)
- call assert_notequal(buf1, buf2)
- call assert_equal(1, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- call assert_equal(0, bufloaded(buf1))
- exe 'bwipe ' .. buf1
- call assert_equal(0, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- exe 'bwipe ' .. buf2
- call assert_equal(0, bufexists(buf2))
+ sandbox let F = {-> "normal ix\<Esc>"->execute()}
+ call assert_fails('call F()', 'E48:')
+ unlet F
- bwipe someName
- bwipe XotherName
- call assert_equal(0, bufexists('someName'))
- call delete('XotherName')
+ call assert_fails('call Fsandbox()', 'E48:')
+ delfunc Fsandbox
+endfunc
+
+func EditAnotherFile()
+ let word = expand('<cword>')
+ edit Xfuncrange2
+endfunc
+
+func Test_func_range_with_edit()
+ " Define a function that edits another buffer, then call it with a range that
+ " is invalid in that buffer.
+ call writefile(['just one line'], 'Xfuncrange2')
+ new
+ eval 10->range()->setline(1)
+ write Xfuncrange1
+ call assert_fails('5,8call EditAnotherFile()', 'E16:')
+
+ call delete('Xfuncrange1')
+ call delete('Xfuncrange2')
+ bwipe!
+endfunc
+
+func Test_func_exists_on_reload()
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists')
+ call assert_equal(0, exists('*ExistingFunction'))
+ source Xfuncexists
+ call assert_equal(1, '*ExistingFunction'->exists())
+ " Redefining a function when reloading a script is OK.
+ source Xfuncexists
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ " But redefining in another script is not OK.
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2')
+ call assert_fails('source Xfuncexists2', 'E122:')
+
+ delfunc ExistingFunction
+ call assert_equal(0, exists('*ExistingFunction'))
+ call writefile([
+ \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
+ \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
+ \ ], 'Xfuncexists')
+ call assert_fails('source Xfuncexists', 'E122:')
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ call delete('Xfuncexists2')
+ call delete('Xfuncexists')
+ delfunc ExistingFunction
+endfunc
+
+func Test_platform_name()
+ " The system matches at most only one name.
+ let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
+ call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
+
+ " Is Unix?
+ call assert_equal(has('beos'), has('beos') && has('unix'))
+ call assert_equal(has('bsd'), has('bsd') && has('unix'))
+ call assert_equal(has('hpux'), has('hpux') && has('unix'))
+ call assert_equal(has('linux'), has('linux') && has('unix'))
+ call assert_equal(has('mac'), has('mac') && has('unix'))
+ call assert_equal(has('qnx'), has('qnx') && has('unix'))
+ call assert_equal(has('sun'), has('sun') && has('unix'))
+ call assert_equal(has('win32'), has('win32') && !has('unix'))
+ call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
+
+ if has('unix') && executable('uname')
+ let uname = system('uname')
+ call assert_equal(uname =~? 'BeOS', has('beos'))
+ " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
+ call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
+ call assert_equal(uname =~? 'HP-UX', has('hpux'))
+ call assert_equal(uname =~? 'Linux', has('linux'))
+ call assert_equal(uname =~? 'Darwin', has('mac'))
+ call assert_equal(uname =~? 'QNX', has('qnx'))
+ call assert_equal(uname =~? 'SunOS', has('sun'))
+ call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
+ endif
endfunc
func Test_readdir()
@@ -1714,6 +1675,49 @@ func Test_eventhandler()
call assert_equal(0, eventhandler())
endfunc
+func Test_bufadd_bufload()
+ call assert_equal(0, bufexists('someName'))
+ let buf = bufadd('someName')
+ call assert_notequal(0, buf)
+ call assert_equal(1, bufexists('someName'))
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ call bufload(buf)
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal([''], getbufline(buf, 1, '$'))
+
+ let curbuf = bufnr('')
+ eval ['some', 'text']->writefile('XotherName')
+ let buf = 'XotherName'->bufadd()
+ call assert_notequal(0, buf)
+ eval 'XotherName'->bufexists()->assert_equal(1)
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ eval buf->bufload()
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
+ call assert_equal(curbuf, bufnr(''))
+
+ let buf1 = bufadd('')
+ let buf2 = bufadd('')
+ call assert_notequal(0, buf1)
+ call assert_notequal(0, buf2)
+ call assert_notequal(buf1, buf2)
+ call assert_equal(1, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ call assert_equal(0, bufloaded(buf1))
+ exe 'bwipe ' .. buf1
+ call assert_equal(0, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ exe 'bwipe ' .. buf2
+ call assert_equal(0, bufexists(buf2))
+
+ bwipe someName
+ bwipe XotherName
+ call assert_equal(0, bufexists('someName'))
+ call delete('XotherName')
+endfunc
+
" Test for the eval() function
func Test_eval()
call assert_fails("call eval('5 a')", 'E488:')
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 8edc9c2608..ad561baf4a 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -36,6 +36,36 @@ func Test_global_error()
call assert_fails('g/\(/y', 'E476:')
endfunc
+" Test for printing lines using :g with different search patterns
+func Test_global_print()
+ new
+ call setline(1, ['foo', 'bar', 'foo', 'foo'])
+ let @/ = 'foo'
+ let t = execute("g/")->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ " Test for Vi compatible patterns
+ let @/ = 'bar'
+ let t = execute('g\/')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ normal gg
+ s/foo/foo/
+ let t = execute('g\&')->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ let @/ = 'bar'
+ let t = execute('g?')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ " Test for the 'Pattern found in every line' message
+ let v:statusmsg = ''
+ v/foo\|bar/p
+ call assert_notequal('', v:statusmsg)
+
+ close!
+endfunc
+
func Test_wrong_delimiter()
call assert_fails('g x^bxd', 'E146:')
endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index e91dea1040..b2d943be00 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -12,6 +12,18 @@ endfunc
func Test_help_errors()
call assert_fails('help doesnotexist', 'E149:')
call assert_fails('help!', 'E478:')
+ if has('multi_lang')
+ call assert_fails('help help@xy', 'E661:')
+ endif
+
+ let save_hf = &helpfile
+ set helpfile=help_missing
+ help
+ call assert_equal(1, winnr('$'))
+ call assert_notequal('help', &buftype)
+ let &helpfile = save_hf
+
+ call assert_fails('help ' . repeat('a', 1048), 'E149:')
new
set keywordprg=:help
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index a6494c531c..a43889b57e 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -23,6 +23,11 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*bar\*')
helpclose
+ help "
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quote\*')
+ helpclose
+
help "*
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*quotestar\*')
@@ -86,11 +91,40 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
helpclose
+ help i^x^y
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
+ helpclose
+
+ exe "help i\<C-\>\<C-G>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
+ helpclose
+
exec "help \<C-V>"
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*CTRL-V\*')
helpclose
+ help /\|
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\bar\*')
+ helpclose
+
+ help CTRL-\_CTRL-N
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
+ helpclose
+
+ help `:pwd`,
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:pwd\*')
+ helpclose
+
+ help `:ls`.
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
exec "help! ('textwidth'"
call assert_equal("help", &filetype)
@@ -122,6 +156,15 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*{address}\*')
helpclose
+ " Use special patterns in the help tag
+ for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
+ exec "help! " . h
+ call assert_equal("help", &filetype)
+ let pat = '\*' . escape(h, '\$[') . '\*'
+ call assert_true(getline('.') =~ pat, pat)
+ helpclose
+ endfor
+
exusage
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*:index\*')
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 899eb530ec..6971ecd357 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -597,6 +597,31 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_colorcolumn()
+ CheckScreendump
+
+ " check that setting 'colorcolumn' when entering a buffer works
+ let lines =<< trim END
+ split
+ edit X
+ call setline(1, ["1111111111","22222222222","3333333333"])
+ set nomodified
+ set colorcolumn=3,9
+ set number cursorline cursorlineopt=number
+ wincmd w
+ buf X
+ END
+ call writefile(lines, 'Xtest_colorcolumn')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
+ call term_sendkeys(buf, ":\<CR>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn')
+endfunc
+
func Test_colorcolumn_bri()
CheckScreendump
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index 10c6164c7c..aa66d86af1 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -620,6 +620,49 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
endfunc
+" reduce a list or a blob
+func Test_reduce()
+ call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
+ call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
+ call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ let l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], { acc, val -> acc + val }))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
+ call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+
+ call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
+
+ call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(v:_null_list, function('add'), 42))
+ call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
+endfunc
+
" splitting a string to a List
func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb '))
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim
new file mode 100644
index 0000000000..abcc9b40c1
--- /dev/null
+++ b/src/nvim/testdir/test_matchfuzzy.vim
@@ -0,0 +1,248 @@
+" Tests for fuzzy matching
+
+source shared.vim
+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("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:')
+ call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:')
+ call assert_equal([], matchfuzzy([], 'abc'))
+ call assert_equal([], matchfuzzy(['abc'], ''))
+ call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
+ call assert_equal([], matchfuzzy([10, 20], 'ac'))
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
+ call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
+ call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
+ call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
+ call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
+ call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
+ call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
+ call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
+ " matches with same score should not be reordered
+ let l = ['abc1', 'abc2', 'abc3']
+ call assert_equal(l, l->matchfuzzy('abc'))
+
+ " Tests for match preferences
+ " preference for camel case match
+ call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
+ " preference for leading letter match
+ call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo'))
+ " preference for sequential match
+ call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one'))
+ " prefer complete matches over separator matches
+ call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc'))
+ " gap penalty
+ call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
+ " path separator vs word separator
+ call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
+ call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two'))
+ call assert_equal([], ['foo bar']->matchfuzzy(" \t "))
+
+ " test for matching a sequence of words
+ call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1}))
+ call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true}))
+
+ %bw!
+ eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
+ let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl')
+ call assert_equal(1, len(l))
+ call assert_match('needle', l[0])
+
+ " Test for fuzzy matching dicts
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:')
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
+ 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', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+ " matches with same score should not be reordered
+ let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}]
+ call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'}))
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ " Nvim supports utf-8 encoding only
+ " set encoding=latin1
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ let &encoding = save_enc
+endfunc
+
+" Test for the matchfuzzypos() function
+func Test_matchfuzzypos()
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
+ call assert_equal([['hello', 'hello world hello world'],
+ \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
+ \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
+ call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
+ call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
+ call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
+ let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
+ call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
+
+ " match in a long string
+ call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
+
+ " preference for camel case match
+ call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
+ " preference for leading letter match
+ call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
+ " preference for sequential match
+ call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
+ " best recursive match
+ call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
+
+ " match multiple words (separated by space)
+ call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
+ call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
+ call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
+
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
+ 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', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
+endfunc
+
+" Test for matchfuzzy() with multibyte characters
+func Test_matchfuzzy_mbyte()
+ CheckFeature multi_lang
+ call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal(['αβΩxxx', 'xαxβxΩx'],
+ \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['세 마리의 작은 돼지'], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('돼지 마리의'))
+ call assert_equal([], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('파란 하늘'))
+
+ " preference for camel case match
+ call assert_equal(['oneĄwo', 'oneąwo'],
+ \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
+ " preference for complete match then match after separator (_ or space)
+ call assert_equal(['ⅠⅡabㄟㄠ'] + sort(['ⅠⅡa_bㄟㄠ', 'ⅠⅡa bㄟㄠ']),
+ \ ['ⅠⅡabㄟㄠ', 'ⅠⅡa bㄟㄠ', 'ⅠⅡa_bㄟㄠ']->matchfuzzy('ⅠⅡabㄟㄠ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['ㄓㄔabㄟㄠ', 'ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ'],
+ \ ['ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ', 'ㄓㄔabㄟㄠ']->matchfuzzy('ㄓㄔabㄟㄠ'))
+ " preference for leading letter match
+ call assert_equal(['ŗŝţũŵż', 'xŗŝţũŵż'],
+ \ ['xŗŝţũŵż', 'ŗŝţũŵż']->matchfuzzy('ŗŝţũŵż'))
+ " preference for sequential match
+ call assert_equal(['ㄞㄡㄤfffifl', 'ㄞaㄡbㄤcffdfiefl'],
+ \ ['ㄞaㄡbㄤcffdfiefl', 'ㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xㄞㄡㄤfffifl', 'xxㄞㄡㄤfffifl'],
+ \ ['xxㄞㄡㄤfffifl', 'xㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['ŗŝţ', 'ŗŝţx', 'ŗŝţxx'],
+ \ ['ŗŝţxx', 'ŗŝţ', 'ŗŝţx']->matchfuzzy('ŗŝţ'))
+endfunc
+
+" Test for matchfuzzypos() with multibyte characters
+func Test_matchfuzzypos_mbyte()
+ CheckFeature multi_lang
+ call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]],
+ \ matchfuzzypos(['こんにちは世界'], 'こんにちは'))
+ call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
+ \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
+ \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+ call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
+ \ matchfuzzypos(['ααααααα'], 'ααα'))
+
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl'))
+ let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
+
+ " match multiple words (separated by space)
+ call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
+ call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘'))
+
+ " match in a long string
+ call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
+ " preference for camel case match
+ call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
+ " preference for leading letter match
+ call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ'))
+ " preference for sequential match
+ call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
+ " best recursive match
+ call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 5b7cf6fee5..f45cd96733 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -1118,7 +1118,7 @@ func Test_normal20_exmode()
endif
call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript')
call writefile(['1', '2'], 'Xfile')
- call system(v:progpath .' -e -s < Xscript Xfile')
+ call system(GetVimCommand() .. ' -e -s < Xscript Xfile')
let a=readfile('Xfile2')
call assert_equal(['1', 'foo', 'bar', '2'], a)
@@ -1171,13 +1171,13 @@ func Test_normal22_zet()
endfor
call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins'
- call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
+ let args = ' -N -i NONE --noplugins -X --headless'
+ call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
let a = readfile('Xfile_Test_normal22_zet')
call assert_equal([], a)
" Test for ZQ
call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
+ call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
let a = readfile('Xfile_Test_normal22_zet')
call assert_equal(['1', '2'], a)
@@ -2779,4 +2779,25 @@ func Test_normal_gj_on_extra_wide_char()
bw!
endfunc
+func Test_normal_count_out_of_range()
+ new
+ call setline(1, 'text')
+ normal 44444444444|
+ call assert_equal(999999999, v:count)
+ normal 444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444444444|
+ call assert_equal(999999999, v:count)
+
+ normal 9y99999999|
+ call assert_equal(899999991, v:count)
+ normal 10y99999999|
+ call assert_equal(999999999, v:count)
+ normal 44444444444y44444444444|
+ call assert_equal(999999999, v:count)
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index a5adb5ff16..8612b7013b 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -314,21 +314,50 @@ func Test_set_errors()
set modifiable&
endfunc
+func CheckWasSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+endfunc
+func CheckWasNotSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_notmatch('Last set from', verb_cm)
+endfunc
+
" Must be executed before other tests that set 'term'.
func Test_000_term_option_verbose()
if has('nvim') || has('gui_running')
return
endif
- let verb_cm = execute('verbose set t_cm')
- call assert_notmatch('Last set from', verb_cm)
+
+ call CheckWasNotSet('t_cm')
let term_save = &term
set term=ansi
- let verb_cm = execute('verbose set t_cm')
- call assert_match('Last set from.*test_options.vim', verb_cm)
+ call CheckWasSet('t_cm')
let &term = term_save
endfunc
+func Test_copy_context()
+ setlocal list
+ call CheckWasSet('list')
+ split
+ call CheckWasSet('list')
+ quit
+ setlocal nolist
+
+ set ai
+ call CheckWasSet('ai')
+ set filetype=perl
+ call CheckWasSet('filetype')
+ set fo=tcroq
+ call CheckWasSet('fo')
+
+ split Xsomebuf
+ call CheckWasSet('ai')
+ call CheckWasNotSet('filetype')
+ call CheckWasSet('fo')
+endfunc
+
func Test_set_ttytype()
" Nvim does not support 'ttytype'.
if !has('nvim') && !has('gui_running') && has('unix')
@@ -656,6 +685,19 @@ func Test_buftype()
close!
endfunc
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_packadd.vim b/src/nvim/testdir/test_packadd.vim
new file mode 100644
index 0000000000..fcb8b8033b
--- /dev/null
+++ b/src/nvim/testdir/test_packadd.vim
@@ -0,0 +1,361 @@
+" Tests for 'packpath' and :packadd
+
+
+func SetUp()
+ let s:topdir = getcwd() . '/Xdir'
+ exe 'set packpath=' . s:topdir
+ let s:plugdir = s:topdir . '/pack/mine/opt/mytest'
+endfunc
+
+func TearDown()
+ call delete(s:topdir, 'rf')
+endfunc
+
+func Test_packadd()
+ if !exists('s:plugdir')
+ echomsg 'when running this test manually, call SetUp() first'
+ return
+ endif
+
+ call mkdir(s:plugdir . '/plugin/also', 'p')
+ call mkdir(s:plugdir . '/ftdetect', 'p')
+ call mkdir(s:plugdir . '/after', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ let rtp_entries = split(rtp, ',')
+ for entry in rtp_entries
+ if entry =~? '\<after\>'
+ let first_after_entry = entry
+ break
+ endif
+ endfor
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+
+ exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
+ call setline(1, 'let g:plugin_also_works = 77')
+ wq
+
+ exe 'split ' . s:plugdir . '/ftdetect/test.vim'
+ call setline(1, 'let g:ftdetect_works = 17')
+ wq
+
+ packadd mytest
+
+ call assert_equal(42, g:plugin_works)
+ call assert_equal(77, g:plugin_also_works)
+ call assert_equal(17, g:ftdetect_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+
+ let new_after = match(&rtp, '/testdir/Xdir/pack/mine/opt/mytest/after,')
+ let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
+ let old_after = match(&rtp, ',' . forwarded . '\>')
+ call assert_true(new_after > 0, 'rtp is ' . &rtp)
+ call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
+ call assert_true(new_after < old_after, 'rtp is ' . &rtp)
+
+ " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
+ call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
+ let rtp = &rtp
+ packadd myte
+
+ " Check the path of 'myte' is added
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/myte\($\|,\)', &rtp)
+
+ " Check exception
+ call assert_fails("packadd directorynotfound", 'E919:')
+ call assert_fails("packadd", 'E471:')
+endfunc
+
+func Test_packadd_start()
+ let plugdir = s:topdir . '/pack/mine/start/other'
+ call mkdir(plugdir . '/plugin', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ exe 'split ' . plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 24')
+ wq
+
+ packadd other
+
+ call assert_equal(24, g:plugin_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/start/other\($\|,\)', &rtp)
+endfunc
+
+func Test_packadd_noload()
+ call mkdir(s:plugdir . '/plugin', 'p')
+ call mkdir(s:plugdir . '/syntax', 'p')
+ set rtp&
+ let rtp = &rtp
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+ let g:plugin_works = 0
+
+ packadd! mytest
+
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+ call assert_equal(0, g:plugin_works)
+
+ " check the path is not added twice
+ let new_rtp = &rtp
+ packadd! mytest
+ call assert_equal(new_rtp, &rtp)
+endfunc
+
+func Test_packadd_symlink_dir()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym'
+ call mkdir(real_dir, 'p')
+ exec "silent !ln -s Xsym" top2_dir
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 44')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(44, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir
+endfunc
+
+func Test_packadd_symlink_dir2()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym/pack'
+ call mkdir(top2_dir, 'p')
+ call mkdir(real_dir, 'p')
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 48')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(48, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir . '/pack'
+ exec "silent !rmdir" top2_dir
+endfunc
+
+" Check command-line completion for 'packadd'
+func Test_packadd_completion()
+ let optdir1 = &packpath . '/pack/mine/opt'
+ let optdir2 = &packpath . '/pack/candidate/opt'
+
+ call mkdir(optdir1 . '/pluginA', 'p')
+ call mkdir(optdir1 . '/pluginC', 'p')
+ call mkdir(optdir2 . '/pluginB', 'p')
+ call mkdir(optdir2 . '/pluginC', 'p')
+
+ let li = []
+ call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("packadd pluginA", li[0])
+ call assert_equal("packadd pluginB", li[1])
+ call assert_equal("packadd pluginC", li[2])
+ call assert_equal("packadd ", li[3])
+endfunc
+
+func Test_packloadall()
+ " plugin foo with an autoload directory
+ let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
+ call mkdir(fooplugindir, 'p')
+ call writefile(['let g:plugin_foo_number = 1234',
+ \ 'let g:plugin_foo_auto = bbb#value',
+ \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
+ let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
+ call mkdir(fooautodir, 'p')
+ call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
+
+ " plugin aaa with an autoload directory
+ let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
+ call mkdir(aaaplugindir, 'p')
+ call writefile(['let g:plugin_aaa_number = 333',
+ \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
+ let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
+ call mkdir(aaaautodir, 'p')
+ call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
+
+ " plugin extra with only an autoload directory
+ let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
+ call mkdir(extraautodir, 'p')
+ call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
+
+ packloadall
+ call assert_equal(1234, g:plugin_foo_number)
+ call assert_equal(55, g:plugin_foo_auto)
+ call assert_equal(99, g:plugin_extra_auto)
+ call assert_equal(333, g:plugin_aaa_number)
+ call assert_equal(77, g:plugin_aaa_auto)
+
+ " only works once
+ call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim')
+ packloadall
+ call assert_false(exists('g:plugin_bar_number'))
+
+ " works when ! used
+ packloadall!
+ call assert_equal(4321, g:plugin_bar_number)
+endfunc
+
+func Test_helptags()
+ let docdir1 = &packpath . '/pack/mine/start/foo/doc'
+ let docdir2 = &packpath . '/pack/mine/start/bar/doc'
+ call mkdir(docdir1, 'p')
+ call mkdir(docdir2, 'p')
+ call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
+ call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
+ exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
+
+ helptags ALL
+
+ let tags1 = readfile(docdir1 . '/tags')
+ call assert_match('look-here', tags1[0])
+ let tags2 = readfile(docdir2 . '/tags')
+ call assert_match('look-away', tags2[0])
+
+ call assert_fails('helptags abcxyz', 'E150:')
+endfunc
+
+func Test_colorscheme()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ colorscheme one
+ call assert_equal(1, g:found_one)
+ colorscheme two
+ call assert_equal(1, g:found_two)
+ colorscheme three
+ call assert_equal(1, g:found_three)
+endfunc
+
+func Test_colorscheme_completion()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let li=[]
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("colorscheme one", li[0])
+ call assert_equal("colorscheme three", li[1])
+ call assert_equal("colorscheme two", li[2])
+ call assert_equal("colorscheme ", li[3])
+endfunc
+
+func Test_runtime()
+ let rundir = &packpath . '/runtime/extra'
+ let startdir = &packpath . '/pack/mine/start/foo/extra'
+ let optdir = &packpath . '/pack/mine/opt/bar/extra'
+ call mkdir(rundir, 'p')
+ call mkdir(startdir, 'p')
+ call mkdir(optdir, 'p')
+ call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
+ call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
+ call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
+ call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
+ call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let g:sequence = ''
+ runtime extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime START extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime OPT extra/bar.vim
+ call assert_equal('opt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime! PACK extra/bar.vim
+ call assert_equal('startopt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+
+ let g:sequence = ''
+ runtime ALL extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime ALL extra/foo.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/bar.vim
+ call assert_equal('runstartopt', g:sequence)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index 4b0097617e..fdb6f13e2b 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -1,8 +1,9 @@
" Test Vim profiler
-if !has('profile')
- finish
-endif
+source check.vim
+CheckFeature profile
+
+source shared.vim
source screendump.vim
func Test_profile_func()
@@ -37,7 +38,7 @@ func Test_profile_func()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
+ call system(GetVimCommand()
\ . ' -es --clean'
\ . ' -c "so Xprofile_func.vim"'
\ . ' -c "qall!"')
@@ -124,8 +125,8 @@ func Test_profile_func_with_ifelse()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
\ . ' -c "profile start Xprofile_func.log"'
\ . ' -c "profile func Foo*"'
\ . ' -c "so Xprofile_func.vim"'
@@ -237,8 +238,8 @@ func Test_profile_func_with_trycatch()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
\ . ' -c "profile start Xprofile_func.log"'
\ . ' -c "profile func Foo*"'
\ . ' -c "so Xprofile_func.vim"'
@@ -324,8 +325,8 @@ func Test_profile_file()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
- \ . ' -es --clean'
+ call system(GetVimCommandClean()
+ \ . ' -es'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
\ . ' -c "so Xprofile_file.vim"'
@@ -369,8 +370,8 @@ func Test_profile_file_with_cont()
\ ]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommandClean()
+ \ . ' -es'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
\ . ' -c "so Xprofile_file.vim"'
@@ -427,7 +428,7 @@ func Test_profile_truncate_mbyte()
\ ]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
+ call system(GetVimCommandClean()
\ . ' -es --cmd "set enc=utf-8"'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
@@ -474,7 +475,7 @@ func Test_profdel_func()
call Foo3()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
let lines = readfile('Xprofile_file.log')
@@ -509,7 +510,7 @@ func Test_profdel_star()
call Foo()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
let lines = readfile('Xprofile_file.log')
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
index ed76709a56..65232175c6 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/src/nvim/testdir/test_put.vim
@@ -138,6 +138,50 @@ func Test_p_with_count_leaves_mark_at_end()
bwipe!
endfunc
+func Test_very_large_count()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ let @" = repeat('x', 100)
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_64bit()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ let @" = repeat('x', 100)
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>99ly"
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block_64bit()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>$y"
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
func Test_put_above_first_line()
new
let @" = 'text'
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index f137ed5346..6852f53ea8 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -32,7 +32,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args>
command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
@@ -69,7 +69,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
@@ -1384,6 +1384,29 @@ func Test_efm_error_type()
let &efm = save_efm
endfunc
+" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
+func Test_efm_end_lnum_col()
+ let save_efm = &efm
+
+ " single line
+ set efm=%f:%l-%e:%c-%k:%t:%m
+ cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
+ \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
+
+ " multiple lines
+ set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
+ cexpr ["1)msg1", "Xfile1:14-24:1-2",
+ \ "2)msg2", "Xfile1:24-34:3-4"]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
+ \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
+ let &efm = save_efm
+endfunc
+
func XquickfixChangedByAutocmd(cchar)
call s:setup_commands(a:cchar)
if a:cchar == 'c'
@@ -2716,7 +2739,7 @@ func Test_cwindow_jump()
call assert_true(winnr('$') == 2)
call assert_true(winnr() == 1)
- " Jumping to a file from the location list window should find a usuable
+ " Jumping to a file from the location list window should find a usable
" window by wrapping around the window list.
enew | only
call setloclist(0, [], 'f')
@@ -5028,6 +5051,52 @@ func Test_qfbuf_update()
call Xqfbuf_update('l')
endfunc
+" Test for the :vimgrep 'f' flag (fuzzy match)
+func Xvimgrep_fuzzy_match(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xvimgrep /three one/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+
+ Xvimgrep /the/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+
+ Xvimgrep /aaa/fg Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(4, len(l))
+ call assert_equal(['Xfile1', 2, 1, 'aaaaaa'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile1', 2, 4, 'aaaaaa'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+ call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'],
+ \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text])
+
+ call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:')
+endfunc
+
+func Test_vimgrep_fuzzy_match()
+ call writefile(['one two three', 'aaaaaa'], 'Xfile1')
+ call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2')
+ call Xvimgrep_fuzzy_match('c')
+ call Xvimgrep_fuzzy_match('l')
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
" Test for getting a specific item from a quickfix list
func Xtest_getqflist_by_idx(cchar)
call s:setup_commands(a:cchar)
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 2e92d9aa2f..23e39eba35 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -219,7 +219,7 @@ func Test_set_register()
call setreg('=', 'b', 'a')
call assert_equal('regwrite', getreg('='))
- " Test for settting a list of lines to special registers
+ " Test for setting a list of lines to special registers
call setreg('/', [])
call assert_equal('', @/)
call setreg('=', [])
diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim
index e7b8946ccf..1650a03876 100644
--- a/src/nvim/testdir/test_retab.vim
+++ b/src/nvim/testdir/test_retab.vim
@@ -69,6 +69,8 @@ func Test_retab()
call assert_equal(" a b c ", Retab('!', 3))
call assert_equal(" a b c ", Retab('', 5))
call assert_equal(" a b c ", Retab('!', 5))
+
+ set tabstop& expandtab&
endfunc
func Test_retab_error()
@@ -78,3 +80,22 @@ func Test_retab_error()
call assert_fails('ret 10000', 'E475:')
call assert_fails('ret 80000000000000000000', 'E475:')
endfunc
+
+func Test_retab_endless()
+ new
+ call setline(1, "\t0\t")
+ let caught = 'no'
+ try
+ while 1
+ set ts=4000
+ retab 4
+ endwhile
+ catch /E1240/
+ let caught = 'yes'
+ endtry
+ bwipe!
+ set tabstop&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 2a1a93b5e3..a3d5ca96a1 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -378,7 +378,6 @@ func Test_searchpair_errors()
call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
@@ -390,7 +389,6 @@ func Test_searchpairpos_errors()
call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim
index 170358e023..d3059664e9 100644
--- a/src/nvim/testdir/test_stat.vim
+++ b/src/nvim/testdir/test_stat.vim
@@ -1,11 +1,13 @@
" Tests for stat functions and checktime
+source check.vim
+
func CheckFileTime(doSleep)
let fnames = ['Xtest1.tmp', 'Xtest2.tmp', 'Xtest3.tmp']
let times = []
let result = 0
- " Use three files istead of localtim(), with a network filesystem the file
+ " Use three files instead of localtim(), with a network filesystem the file
" times may differ at bit
let fl = ['Hello World!']
for fname in fnames
@@ -74,6 +76,44 @@ func Test_checktime()
call delete(fname)
endfunc
+func Test_checktime_fast()
+ CheckFeature nanotime
+
+ let fname = 'Xtest.tmp'
+
+ let fl = ['Hello World!']
+ call writefile(fl, fname)
+ set autoread
+ exec 'e' fname
+ let fl = readfile(fname)
+ let fl[0] .= ' - checktime'
+ sleep 10m " make test less flaky in Nvim
+ call writefile(fl, fname)
+ checktime
+ call assert_equal(fl[0], getline(1))
+
+ call delete(fname)
+endfunc
+
+func Test_autoread_fast()
+ CheckFeature nanotime
+
+ " this is timing sensitive
+ let g:test_is_flaky = 1
+
+ new Xautoread
+ setlocal autoread
+ call setline(1, 'foo')
+ w!
+ sleep 10m
+ call writefile(['bar'], 'Xautoread')
+ sleep 10m
+ checktime
+ call assert_equal('bar', trim(getline(1)))
+
+ call delete('Xautoread')
+endfunc
+
func Test_autoread_file_deleted()
new Xautoread
set autoread
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index ecd980472a..dbb792d2b0 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -51,10 +51,12 @@ func Test_substitute_variants()
\ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
\ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
\ { 'cmd': ':s/t/r/cn', 'exp': ln },
\ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
\ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
\ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
\ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
@@ -86,6 +88,7 @@ func Test_substitute_variants()
\ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
\]
for var in variants
@@ -384,6 +387,10 @@ func Test_substitute_join()
call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
call assert_equal('\n', histget("search", -1))
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ call execute('1,2s/\n//')
+ call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
+
bwipe!
endfunc
@@ -398,6 +405,11 @@ func Test_substitute_count()
call assert_fails('s/foo/bar/0', 'E939:')
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2,4s/foo/bar/ 10
+ call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
+ \ getline(1, '$'))
+
bwipe!
endfunc
@@ -416,6 +428,10 @@ func Test_substitute_flag_n()
" No substitution should have been done.
call assert_equal(lines, getline(1, '$'))
+ %delete _
+ call setline(1, ['A', 'Bar', 'Baz'])
+ call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
+
bwipe!
endfunc
@@ -749,6 +765,45 @@ func Test_sub_beyond_end()
bwipe!
endfunc
+" Test for repeating last substitution using :~ and :&r
+func Test_repeat_last_sub()
+ new
+ call setline(1, ['blue green yellow orange white'])
+ s/blue/red/
+ let @/ = 'yellow'
+ ~
+ let @/ = 'white'
+ :&r
+ let @/ = 'green'
+ s//gray
+ call assert_equal('red gray red orange red', getline(1))
+ close!
+endfunc
+
+" Test for Vi compatible substitution:
+" \/{string}/, \?{string}? and \&{string}&
+func Test_sub_vi_compatibility()
+ new
+ call setline(1, ['blue green yellow orange blue'])
+ let @/ = 'orange'
+ s\/white/
+ let @/ = 'blue'
+ s\?amber?
+ let @/ = 'white'
+ s\&green&
+ call assert_equal('amber green yellow white green', getline(1))
+ close!
+endfunc
+
+" Test for substitute with the new text longer than the original text
+func Test_sub_expand_text()
+ new
+ call setline(1, 'abcabcabcabcabcabcabcabc')
+ s/b/\=repeat('B', 10)/g
+ call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
+ close!
+endfunc
+
func Test_submatch_list_concatenate()
let pat = 'A\(.\)'
let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
index 4b3bd5eadf..bf88bd4453 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/src/nvim/testdir/test_suspend.vim
@@ -26,8 +26,8 @@ func Test_suspend()
" Wait for shell prompt.
call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call term_sendkeys(buf, v:progpath
- \ . " --clean -X"
+ call term_sendkeys(buf, GetVimCommandClean()
+ \ . " -X"
\ . " -c 'set nu'"
\ . " -c 'call setline(1, \"foo\")'"
\ . " Xfoo\<CR>")
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 757866f5dc..b047b53b6f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -728,6 +728,56 @@ func Test_syntax_foldlevel()
quit!
endfunc
+func Test_search_syntax_skip()
+ new
+ let lines =<< trim END
+
+ /* This is VIM */
+ Another Text for VIM
+ let a = "VIM"
+ END
+ call setline(1, lines)
+ syntax on
+ syntax match Comment "^/\*.*\*/"
+ syntax match String '".*"'
+
+ " Skip argument using string evaluation.
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
+ call assert_equal('Another Text for VIM', getline('.'))
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using Lambda.
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using funcref.
+ func InComment()
+ return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
+ endfunc
+ func InString()
+ return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
+ endfunc
+ 1
+ call search('VIM', 'w', '', 0, function('InComment'))
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, function('InString'))
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ delfunc InComment
+ delfunc InString
+ bwipe!
+endfunc
+
func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name"
new
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
index 5b8079d7b6..18692f42c9 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -50,11 +50,11 @@ endfunc
func Test_system_exmode()
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"'
+ let cmd = ' -es -c "source Xscript" +q; echo "result=$?"'
" Need to put this in a script, "catch" isn't found after an unknown
" function.
call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
- let a = system(v:progpath . cmd)
+ let a = system(GetVimCommand() . cmd)
call assert_match('result=0', a)
call assert_equal(0, v:shell_error)
endif
@@ -62,33 +62,33 @@ func Test_system_exmode()
" Error before try does set error flag.
call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
if has('unix') " echo $? only works on Unix
- let a = system(v:progpath . cmd)
+ let a = system(GetVimCommand() . cmd)
call assert_notequal('0', a[0])
endif
- let cmd = ' -es --headless -u NONE -c "source Xscript" +q'
- let a = system(v:progpath . cmd)
+ let cmd = ' -es -c "source Xscript" +q'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, v:shell_error)
call delete('Xscript')
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, a[0])
endif
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()" +q'
+ let a = system(GetVimCommand(). cmd)
call assert_notequal(0, v:shell_error)
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, a[0])
endif
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, v:shell_error)
endfunc
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
index 117d962d08..3a18206078 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/src/nvim/testdir/test_tabline.vim
@@ -86,6 +86,17 @@ func Test_tabline_empty_group()
set tabline=
endfunc
+" When there are exactly 20 tabline format items (the exact size of the
+" initial tabline items array), test that we don't write beyond the size
+" of the array.
+func Test_tabline_20_format_items_no_overrun()
+ set showtabline=2
+
+ let tabline = repeat('%#StatColorHi2#', 20)
+ let &tabline = tabline
+ redrawtabline
+ set showtabline& tabline&
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 052c32214d..e9f846af7b 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -238,7 +238,33 @@ func Test_format_c_comment()
END
call assert_equal(expected, getline(1, '$'))
- " Using "o" repeats the line comment, "O" does not.
+ " Using either "o" or "O" repeats a line comment occupying a whole line.
+ %del
+ let text =<< trim END
+ nop;
+ // This is a comment
+ val = val;
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+ //
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" repeats a line comment after a statement, "O" does not.
%del
let text =<< trim END
nop;
@@ -531,6 +557,21 @@ func Test_format_align()
call assert_equal("\t\t Vim", getline(1))
q!
+ " align text with 'rightleft'
+ if has('rightleft')
+ new
+ call setline(1, 'Vim')
+ setlocal rightleft
+ left 20
+ setlocal norightleft
+ call assert_equal("\t\t Vim", getline(1))
+ setlocal rightleft
+ right
+ setlocal norightleft
+ call assert_equal("Vim", getline(1))
+ close!
+ endif
+
set tw&
endfunc
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index 36776d5a64..9b010a5dbc 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -8,7 +8,7 @@ func Test_visual_block_insert()
new
call setline(1, ["aaa", "あああ", "bbb"])
exe ":norm! gg0l\<C-V>jjIx\<Esc>"
- call assert_equal(['axaa', 'xあああ', 'bxbb'], getline(1, '$'))
+ call assert_equal(['axaa', ' xあああ', 'bxbb'], getline(1, '$'))
bwipeout!
endfunc
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 75a965f16d..f93eb6e274 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1,6 +1,9 @@
" Test various aspects of the Vim script language.
" Most of this was formerly in test49.
+source check.vim
+source shared.vim
+
"-------------------------------------------------------------------------------
" Test environment {{{1
"-------------------------------------------------------------------------------
@@ -1744,7 +1747,7 @@ func Test_function_defined_line()
[CODE]
call writefile(lines, 'Xtest.vim')
- let res = system(v:progpath .. ' --clean -es -X -S 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:]]*')
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 76274fb038..ae8f9ba70b 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -22,6 +22,14 @@ func Test_block_shift_overflow()
q!
endfunc
+func Test_dotregister_paste()
+ new
+ exe "norm! ihello world\<esc>"
+ norm! 0ve".p
+ call assert_equal('hello world world', getline(1))
+ q!
+endfunc
+
func Test_Visual_ctrl_o()
new
call setline(1, ['one', 'two', 'three'])
@@ -42,14 +50,6 @@ func Test_Visual_vapo()
bwipe!
endfunc
-func Test_dotregister_paste()
- new
- exe "norm! ihello world\<esc>"
- norm! 0ve".p
- call assert_equal('hello world world', getline(1))
- q!
-endfunc
-
func Test_Visual_inner_quote()
new
normal oxX
@@ -57,9 +57,37 @@ func Test_Visual_inner_quote()
bwipe!
endfunc
+" Test for Visual mode not being reset causing E315 error.
+func TriggerTheProblem()
+ " At this point there is no visual selection because :call reset it.
+ " Let's restore the selection:
+ normal gv
+ '<,'>del _
+ try
+ exe "normal \<Esc>"
+ catch /^Vim\%((\a\+)\)\=:E315/
+ echom 'Snap! E315 error!'
+ let g:msg = 'Snap! E315 error!'
+ endtry
+endfunc
+
+func Test_visual_mode_reset()
+ enew
+ let g:msg = "Everything's fine."
+ enew
+ setl buftype=nofile
+ call append(line('$'), 'Delete this line.')
+
+ " NOTE: this has to be done by a call to a function because executing :del
+ " the ex-way will require the colon operator which resets the visual mode
+ " thus preventing the problem:
+ exe "normal! GV:call TriggerTheProblem()\<CR>"
+ call assert_equal("Everything's fine.", g:msg)
+endfunc
+
" Test for visual block shift and tab characters.
func Test_block_shift_tab()
- enew!
+ new
call append(0, repeat(['one two three'], 5))
call cursor(1,1)
exe "normal i\<C-G>u"
@@ -68,7 +96,7 @@ func Test_block_shift_tab()
call assert_equal('on1 two three', getline(2))
call assert_equal('on1 two three', getline(5))
- enew!
+ %d _
call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5))
call cursor(1,1)
exe "normal \<C-V>4jI \<Esc>j<<11|D"
@@ -93,12 +121,26 @@ func Test_block_shift_tab()
call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4))
call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
- enew!
+ " Test for block shift with space characters at the beginning and with
+ " 'noexpandtab' and 'expandtab'
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 noexpandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$'))
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 expandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal([" 1", " 2", " 3"], getline(1, '$'))
+ setlocal shiftwidth&
+
+ bw!
endfunc
" Tests Blockwise Visual when there are TABs before the text.
func Test_blockwise_visual()
- enew!
+ new
call append(0, ['123456',
\ '234567',
\ '345678',
@@ -120,26 +162,31 @@ func Test_blockwise_visual()
\ "\t\tsomext",
\ "\t\ttesext"], getline(1, 7))
- enew!
+ bw!
endfunc
" Test swapping corners in blockwise visual mode with o and O
func Test_blockwise_visual_o_O()
- enew!
+ new
exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr "
exe "norm! gvO\<Esc>ra"
exe "norm! gvO\<Esc>rb"
exe "norm! gvo\<C-c>rc"
exe "norm! gvO\<C-c>rd"
+ set selection=exclusive
+ exe "norm! gvOo\<C-c>re"
+ call assert_equal('...a be.', getline(4))
+ exe "norm! gvOO\<C-c>rf"
+ set selection&
call assert_equal(['..........',
\ '...c d..',
\ '... ..',
- \ '...a b..',
+ \ '...a bf.',
\ '..........'], getline(1, '$'))
- enew!
+ bw!
endfun
" Test Virtual replace mode.
@@ -242,35 +289,6 @@ func Test_virtual_replace2()
set bs&vim
endfunc
-" Test for Visual mode not being reset causing E315 error.
-func TriggerTheProblem()
- " At this point there is no visual selection because :call reset it.
- " Let's restore the selection:
- normal gv
- '<,'>del _
- try
- exe "normal \<Esc>"
- catch /^Vim\%((\a\+)\)\=:E315/
- echom 'Snap! E315 error!'
- let g:msg = 'Snap! E315 error!'
- endtry
-endfunc
-
-func Test_visual_mode_reset()
- enew
- let g:msg = "Everything's fine."
- enew
- setl buftype=nofile
- call append(line('$'), 'Delete this line.')
-
- " NOTE: this has to be done by a call to a function because executing :del
- " the ex-way will require the colon operator which resets the visual mode
- " thus preventing the problem:
- exe "normal! GV:call TriggerTheProblem()\<CR>"
- call assert_equal("Everything's fine.", g:msg)
-
-endfunc
-
func Test_Visual_word_textobject()
new
call setline(1, ['First sentence. Second sentence.'])
@@ -349,17 +367,6 @@ func Test_Visual_sentence_textobject()
bwipe!
endfunc
-func Test_curswant_not_changed()
- new
- call setline(1, ['one', 'two'])
- au InsertLeave * call getcurpos()
- call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
-
- bwipe!
- au! InsertLeave
-endfunc
-
func Test_Visual_paragraph_textobject()
new
call setline(1, ['First line.',
@@ -409,6 +416,17 @@ func Test_Visual_paragraph_textobject()
bwipe!
endfunc
+func Test_curswant_not_changed()
+ new
+ call setline(1, ['one', 'two'])
+ au InsertLeave * call getcurpos()
+ call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+
+ bwipe!
+ au! InsertLeave
+endfunc
+
" Tests for "vaBiB", end could be wrong.
func Test_Visual_Block()
new
@@ -435,24 +453,13 @@ endfunc
" Test for 'p'ut in visual block mode
func Test_visual_block_put()
- enew
-
+ new
call append(0, ['One', 'Two', 'Three'])
normal gg
yank
call feedkeys("jl\<C-V>ljp", 'xt')
call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$'))
-
- enew!
-endfunc
-
-func Test_visual_put_in_block()
- new
- call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
- normal 1G2yl
- exe "normal 1G2l\<C-V>jjlp"
- call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
- bwipe!
+ bw!
endfunc
" Visual modes (v V CTRL-V) followed by an operator; count; repeating
@@ -623,6 +630,17 @@ func Test_characterwise_visual_mode()
normal Gkvj$d
call assert_equal(['', 'a', ''], getline(1, '$'))
+ " characterwise visual mode: replace a single character line and the eol
+ %d _
+ call setline(1, "a")
+ normal v$rx
+ call assert_equal(['x'], getline(1, '$'))
+
+ " replace a character with composing characters
+ call setline(1, "xã̳x")
+ normal gg0lvrb
+ call assert_equal("xbx", getline(1))
+
bwipe!
endfunc
@@ -658,6 +676,16 @@ func Test_characterwise_select_mode()
exe "normal Gkgh\<Down>\<End>\<Del>"
call assert_equal(['', 'a', ''], getline(1, '$'))
+ " CTRL-H in select mode behaves like 'x'
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
+ call assert_equal('ef', getline(1))
+
+ " CTRL-O in select mode switches to visual mode for one command
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<C-O>3lm"
+ call assert_equal('mef', getline(1))
+
sunmap <lt>End>
sunmap <lt>Down>
sunmap <lt>Del>
@@ -757,8 +785,7 @@ endfunc
func Test_visual_block_mode()
new
call append(0, '')
- call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm',
- \ 'abcdefghijklm', 'abcdefghijklm'])
+ call setline(1, repeat(['abcdefghijklm'], 5))
call cursor(1, 1)
" Test shift-right of a block
@@ -777,6 +804,76 @@ func Test_visual_block_mode()
\ 'axyzqqqqefgmnoklm',
\ 'abcdqqqqijklm'], getline(1, 5))
+ " Test 'C' to change till the end of the line
+ call cursor(3, 4)
+ exe "normal! \<C-V>j3lCooo"
+ call assert_equal(['axyooo', 'axyooo'], getline(3, 4))
+
+ " Test 'D' to delete till the end of the line
+ call cursor(3, 3)
+ exe "normal! \<C-V>j2lD"
+ call assert_equal(['ax', 'ax'], getline(3, 4))
+
+ " Test block insert with a short line that ends before the block
+ %d _
+ call setline(1, [" one", "a", " two"])
+ exe "normal gg\<C-V>2jIx"
+ call assert_equal([" xone", "a", " xtwo"], getline(1, '$'))
+
+ " Test block append at EOL with '$' and without '$'
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg$\<C-V>2jAx"
+ call assert_equal(["onex", "ax", "twox"], getline(1, '$'))
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg3l\<C-V>2jAx"
+ call assert_equal(["onex", "a x", "twox"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and use $ to jump to
+ " the end of the line.
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg$\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and move cursor to the
+ " end of the line
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg2l\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Replace odd number of characters with a multibyte character
+ %d _
+ call setline(1, ['abcd', 'efgh'])
+ exe "normal ggl\<C-V>2ljr\u1100"
+ call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$'))
+
+ " During visual block append, if the cursor moved outside of the selected
+ " range, then the edit should not be applied to the block.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal 2G\<C-V>jAx\<Up>"
+ call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$'))
+
+ " During visual block append, if the cursor is moved before the start of the
+ " block, then the new text should be appended there.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal $\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Repeat the previous test but use 'l' to move the cursor instead of '$'
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal! gg2l\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+
+ " Change a characterwise motion to a blockwise motion using CTRL-V
+ %d _
+ call setline(1, ['123', '456', '789'])
+ exe "normal ld\<C-V>j"
+ call assert_equal(['13', '46', '789'], getline(1, '$'))
+
" Test from ':help v_b_I_example'
%d _
setlocal tabstop=8 shiftwidth=4
@@ -1008,6 +1105,15 @@ func Test_block_insert_replace_tabs()
bwipe!
endfunc
+func Test_visual_put_in_block()
+ new
+ call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
+ normal 1G2yl
+ exe "normal 1G2l\<C-V>jjlp"
+ call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
+ bwipe!
+endfunc
+
func Test_visual_put_in_block_using_zp()
new
" paste using zP
@@ -1141,6 +1247,15 @@ func Test_visual_block_ctrl_w_f()
au! BufNew
endfunc
+func Test_visual_block_append_invalid_char()
+ " this was going over the end of the line
+ new
+ call setline(1, [' let xxx', 'xxxxxˆ', 'xxxxxxxxxxx'])
+ exe "normal 0\<C-V>jjA-\<Esc>"
+ call assert_equal([' - let xxx', 'xxxxx -ˆ', 'xxxxxxxx-xxx'], getline(1, 3))
+ bwipe!
+endfunc
+
func Test_visual_reselect_with_count()
" this was causing an illegal memory access
let lines =<< trim END
@@ -1161,6 +1276,25 @@ func Test_visual_reselect_with_count()
call delete('XvisualReselect')
endfunc
+func Test_visual_block_insert_round_off()
+ new
+ " The number of characters are tuned to fill a 4096 byte allocated block,
+ " so that valgrind reports going over the end.
+ call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)])
+ exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>"
+ bwipe!
+endfunc
+
+" this was causing an ml_get error
+func Test_visual_exchange_windows()
+ enew!
+ new
+ call setline(1, ['foo', 'bar'])
+ exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>"
+ bwipe!
+ bwipe!
+endfunc
+
" this was leaving the end of the Visual area beyond the end of a line
func Test_visual_ex_copy_line()
new
@@ -1184,8 +1318,66 @@ func Test_visual_undo_deletes_last_line()
exe "normal ggvjfxO"
undo
normal gNU
+
bwipe!
endfunc
+func Test_visual_paste()
+ new
+
+ " v_p overwrites unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vp
+ call assert_equal('f', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('bazooxxf', getline(1))
+
+ if has('clipboard')
+ " v_P does not overwrite unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxfoo', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vP
+ call assert_equal('baz', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxfoo', getline(1))
+ normal $vP
+ call assert_equal('baz', @")
+ call assert_equal('o', @-)
+ call assert_equal('bazooxxfobaz', getline(1))
+ endif
+
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim
index 7f5b80e8d3..f4878c2397 100644
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ b/src/nvim/testdir/test_winbuf_close.vim
@@ -194,3 +194,22 @@ func Test_tabwin_close()
call assert_true(v:true)
%bwipe!
endfunc
+
+" Test when closing a split window (above/below) restores space to the window
+" below when 'noequalalways' and 'splitright' are set.
+func Test_window_close_splitright_noequalalways()
+ set noequalalways
+ set splitright
+ new
+ let w1 = win_getid()
+ new
+ let w2 = win_getid()
+ execute "normal \<c-w>b"
+ let h = winheight(0)
+ let w = win_getid()
+ new
+ q
+ call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window")
+ call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window")
+endfunc
+
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 5ffbe82082..b42665c9b5 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -169,9 +169,7 @@ endfunc
" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
" command.
func Test_write_pipe_to_cmd()
- if !has('unix')
- return
- endif
+ CheckUnix
new
call setline(1, ['L1', 'L2', 'L3', 'L4'])
2,3w !cat > Xfile
@@ -371,6 +369,154 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Test for writing and reading a file starting with a BOM.
+" Byte Order Mark (BOM) character for various encodings is below:
+" UTF-8 : EF BB BF
+" UTF-16 (BE): FE FF
+" UTF-16 (LE): FF FE
+" UTF-32 (BE): 00 00 FE FF
+" UTF-32 (LE): FF FE 00 00
+func Test_readwrite_file_with_bom()
+ let utf8_bom = "\xEF\xBB\xBF"
+ let utf16be_bom = "\xFE\xFF"
+ let utf16le_bom = "\xFF\xFE"
+ let utf32be_bom = "\n\n\xFE\xFF"
+ let utf32le_bom = "\xFF\xFE\n\n"
+ let save_fileencoding = &fileencoding
+ set cpoptions+=S
+
+ " Check that editing a latin1 file doesn't see a BOM
+ call writefile(["\xFE\xFElatin-1"], 'Xtest1')
+ edit Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ set fenc=latin1
+ write Xfile2
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b'))
+ set bomb fenc=latin1
+ write Xtest3
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b'))
+ set bomb&
+
+ " Check utf-8 BOM
+ %bw!
+ call writefile([utf8_bom .. "utf-8"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-8', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('utf-8', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(['utf-8', ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b'))
+
+ " Check utf-8 with an error (will fall back to latin-1)
+ %bw!
+ call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2 BOM
+ %bw!
+ call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-16', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2
+ w! Xtest3
+ call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2le BOM
+ %bw!
+ call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1')
+ " Need to add a NUL byte after the NL byte
+ call writefile(0z00, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('utf-16le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2le
+ w! Xtest3
+ call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-4 BOM
+ %bw!
+ call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('ucs-4', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4
+ w! Xtest3
+ call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b'))
+
+ " Check ucs-4le BOM
+ %bw!
+ call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1')
+ " Need to add three NUL bytes after the NL byte
+ call writefile(0z000000, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('ucs-4le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4le
+ w! Xtest3
+ call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b'))
+
+ set cpoptions-=S
+ let &fileencoding = save_fileencoding
+ call delete('Xtest1')
+ call delete('Xfile2')
+ call delete('Xtest3')
+ %bw!
+endfunc
+
+func Test_read_write_bin()
+ " write file missing EOL
+ call writefile(['noeol'], "XNoEolSetEol", 'bS')
+ call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
+
+ " when file is read 'eol' is off
+ set nofixeol
+ e! ++ff=unix XNoEolSetEol
+ call assert_equal(0, &eol)
+
+ " writing with 'eol' set adds the newline
+ setlocal eol
+ w
+ call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
+
+ call delete('XNoEolSetEol')
+ set ff&
+ bwipe! XNoEolSetEol
+endfunc
+
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 6e48df3734..6b889cf97c 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -227,7 +227,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
&& !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
&& ASCII_ISUPPER(key->code.codepoint)) {
assert(len <= 62);
- // Make remove for the S-
+ // Make room for the S-
memmove(buf + 3, buf + 1, len - 1);
buf[1] = 'S';
buf[2] = '-';
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 6996a93928..43667377c5 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -304,7 +304,7 @@ newwindow:
newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
- win_close(curwin, false);
+ win_close(curwin, false, false);
}
if (valid_tabpage(newtab)) {
goto_tabpage_tp(newtab, true, true);
@@ -449,7 +449,7 @@ wingotofile:
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
- win_close(curwin, false);
+ win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
} else if (nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
@@ -1482,8 +1482,6 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
copyFoldingState(oldp, newp);
win_init_some(newp, oldp);
-
- didset_window_options(newp);
}
/*
@@ -1729,6 +1727,12 @@ static void win_exchange(long Prenum)
(void)win_comp_pos(); // recompute window positions
+ if (wp->w_buffer != curbuf) {
+ reset_VIsual_and_resel();
+ } else if (VIsual_active) {
+ wp->w_cursor = curwin->w_cursor;
+ }
+
win_enter(wp, true);
redraw_later(curwin, NOT_VALID);
redraw_later(wp, NOT_VALID);
@@ -2290,7 +2294,7 @@ void close_windows(buf_T *buf, int keep_curwin)
for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
- if (win_close(wp, false) == FAIL) {
+ if (win_close(wp, false, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
}
@@ -2368,6 +2372,22 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
}
+/// Check if floating windows can be closed.
+///
+/// @return true if all floating windows can be closed
+static bool can_close_floating_windows(tabpage_T *tab)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, tab) {
+ buf_T *buf = wp->w_buffer;
+ int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
+
+ if (need_hide && !buf_hide(buf)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2432,7 +2452,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
//
// Called by :quit, :close, :xit, :wq and findtag().
// Returns FAIL when the window was not closed.
-int win_close(win_T *win, bool free_buf)
+int win_close(win_T *win, bool free_buf, bool force)
{
win_T *wp;
bool other_buffer = false;
@@ -2462,9 +2482,18 @@ int win_close(win_T *win, bool free_buf)
}
if ((firstwin == win && lastwin_nofloating() == win)
&& lastwin->w_floating) {
- // TODO(bfredl): we might close the float also instead
- emsg(e_floatonly);
- return FAIL;
+ if (force || can_close_floating_windows(curtab)) {
+ win_T *nextwp;
+ for (win_T *wpp = firstwin; wpp != NULL; wpp = nextwp) {
+ nextwp = wpp->w_next;
+ if (wpp->w_floating) {
+ win_close(wpp, free_buf, force);
+ }
+ }
+ } else {
+ emsg(e_floatonly);
+ return FAIL;
+ }
}
// When closing the last window in a tab page first go to another tab page
@@ -3056,9 +3085,21 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
return frp->fr_prev;
}
+ // By default the next window will get the space that was abandoned by this
+ // window
frame_T *target_fr = frp->fr_next;
frame_T *other_fr = frp->fr_prev;
- if (p_spr || p_sb) {
+
+ // If this is part of a column of windows and 'splitbelow' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
+ target_fr = frp->fr_prev;
+ other_fr = frp->fr_next;
+ }
+
+ // If this is part of a row of windows, and 'splitright' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
target_fr = frp->fr_prev;
other_fr = frp->fr_next;
}
@@ -3611,7 +3652,9 @@ void close_others(int message, int forceit)
continue;
}
}
- win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer));
+ win_close(wp,
+ !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer),
+ false);
}
if (message && !ONE_WINDOW) {
@@ -4645,20 +4688,29 @@ void fix_current_dir(void)
globaldir = (char_u *)xstrdup(cwd);
}
}
+ bool dir_differs = pathcmp(new_dir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, true);
+ }
if (os_chdir(new_dir) == 0) {
- if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) {
- do_autocmd_dirchanged(new_dir, curwin->w_localdir
- ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, false);
}
- last_chdir_reason = NULL;
- shorten_fnames(true);
}
+ last_chdir_reason = NULL;
+ shorten_fnames(true);
} else if (globaldir != NULL) {
// Window doesn't have a local directory and we are not in the global
// directory: Change to the global directory.
+ bool dir_differs = pathcmp((char *)globaldir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, true);
+ }
if (os_chdir((char *)globaldir) == 0) {
- if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) {
- do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, false);
}
}
XFREE_CLEAR(globaldir);
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 688f3abba5..dd3af8c28f 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -423,6 +423,13 @@ describe('api/buf', function()
-- will join multiple lines if needed
set_text(0, 6, 3, 4, {'bar'})
eq({'hello bar'}, get_lines(0, 1, true))
+
+ -- can use negative line numbers
+ set_text(-2, 0, -2, 5, {'goodbye'})
+ eq({'goodbye bar', ''}, get_lines(0, -1, true))
+
+ set_text(-1, 0, -1, 0, {'text'})
+ eq({'goodbye bar', 'text'}, get_lines(0, 2, true))
end)
it('works with undo', function()
@@ -537,6 +544,34 @@ describe('api/buf', function()
end)
end)
+ describe('nvim_buf_get_text', function()
+ local get_text = curbufmeths.get_text
+
+ it('works', function()
+ insert([[
+ hello foo!
+ text]])
+
+ eq({'hello'}, get_text(0, 0, 0, 5, {}))
+ eq({'hello foo!'}, get_text(0, 0, 0, 42, {}))
+ eq({'foo!'}, get_text(0, 6, 0, 10, {}))
+ eq({'foo!', 'tex'}, get_text(0, 6, 1, 3, {}))
+ eq({'foo!', 'tex'}, get_text(-2, 6, -1, 3, {}))
+ eq({''}, get_text(0, 18, 0, 20, {}))
+ eq({'ext'}, get_text(-1, 1, -1, 4, {}))
+ end)
+
+ it('errors on out-of-range', function()
+ eq(false, pcall(get_text, 2, 0, 3, 0, {}))
+ eq(false, pcall(get_text, 0, 0, 4, 0, {}))
+ end)
+
+ it('errors when start is greater than end', function()
+ eq(false, pcall(get_text, 1, 0, 0, 0, {}))
+ eq(false, pcall(get_text, 0, 1, 0, 0, {}))
+ end)
+ end)
+
describe('nvim_buf_get_offset', function()
local get_offset = curbufmeths.get_offset
it('works', function()
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index d64d324a88..de22c9078c 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -180,6 +180,28 @@ describe('nvim_add_user_command', function()
feed('<C-U>Test b<Tab>')
eq('Test bbb', funcs.getcmdline())
end)
+
+ it('does not allow invalid command names', function()
+ matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[
+ vim.api.nvim_add_user_command('test', 'echo "hi"', {})
+ ]]))
+
+ matches('Invalid command name', pcall_err(exec_lua, [[
+ vim.api.nvim_add_user_command('t@', 'echo "hi"', {})
+ ]]))
+
+ matches('Invalid command name', pcall_err(exec_lua, [[
+ vim.api.nvim_add_user_command('T@st', 'echo "hi"', {})
+ ]]))
+
+ matches('Invalid command name', pcall_err(exec_lua, [[
+ vim.api.nvim_add_user_command('Test!', 'echo "hi"', {})
+ ]]))
+
+ matches('Invalid command name', pcall_err(exec_lua, [[
+ vim.api.nvim_add_user_command('💩', 'echo "hi"', {})
+ ]]))
+ end)
end)
describe('nvim_del_user_command', function()
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index a74da6073a..6924d41e0b 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -195,10 +195,12 @@ describe("API: set highlight", function()
reverse = true,
undercurl = true,
underline = true,
+ strikethrough = true,
cterm = {
italic = true,
reverse = true,
undercurl = true,
+ strikethrough = true,
}
}
local highlight3_result_gui = {
@@ -209,6 +211,7 @@ describe("API: set highlight", function()
reverse = true,
undercurl = true,
underline = true,
+ strikethrough = true,
}
local highlight3_result_cterm = {
background = highlight_color.ctermbg,
@@ -216,6 +219,7 @@ describe("API: set highlight", function()
italic = true,
reverse = true,
undercurl = true,
+ strikethrough = true,
}
local function get_ns()
@@ -238,6 +242,12 @@ describe("API: set highlight", function()
eq(highlight2_result, meths.get_hl_by_name('Test_hl', false))
end)
+ it ("can set emtpy cterm attr", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', { cterm = {} })
+ eq({}, meths.get_hl_by_name('Test_hl', false))
+ end)
+
it ("cterm attr defaults to gui attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
@@ -264,7 +274,7 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl'))
meths.set_hl(0, 'Test_hl2', highlight3_config)
- eq('Test_hl2 xxx cterm=undercurl,italic,reverse ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,italic,reverse guifg=#ff0000 guibg=#0032aa',
+ eq('Test_hl2 xxx cterm=undercurl,italic,reverse,strikethrough ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,italic,reverse,strikethrough guifg=#ff0000 guibg=#0032aa',
exec_capture('highlight Test_hl2'))
-- Colors are stored exactly as they are defined.
@@ -272,4 +282,46 @@ describe("API: set highlight", function()
eq('Test_hl3 xxx guifg=bLue guibg=reD',
exec_capture('highlight Test_hl3'))
end)
+
+ it ("can modify a highlight in the global namespace", function()
+ meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'})
+ eq('Test_hl3 xxx guifg=blue guibg=red',
+ exec_capture('highlight Test_hl3'))
+
+ meths.set_hl(0, 'Test_hl3', { bg = 'red' })
+ eq('Test_hl3 xxx guibg=red',
+ exec_capture('highlight Test_hl3'))
+
+ meths.set_hl(0, 'Test_hl3', { ctermbg = 9, ctermfg = 12})
+ eq('Test_hl3 xxx ctermfg=12 ctermbg=9',
+ exec_capture('highlight Test_hl3'))
+
+ meths.set_hl(0, 'Test_hl3', { ctermbg = 'red' , ctermfg = 'blue'})
+ eq('Test_hl3 xxx ctermfg=12 ctermbg=9',
+ exec_capture('highlight Test_hl3'))
+
+ meths.set_hl(0, 'Test_hl3', { ctermbg = 9 })
+ eq('Test_hl3 xxx ctermbg=9',
+ exec_capture('highlight Test_hl3'))
+
+ eq("'redd' is not a valid color",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'}))
+
+ eq("'bleu' is not a valid color",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'}))
+
+ meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'})
+ eq('Test_hl3 xxx guifg=#FF00FF',
+ exec_capture('highlight Test_hl3'))
+
+ eq("'#FF00FF' is not a valid color",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'}))
+
+ for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do
+ meths.set_hl(0, 'Test_hl3', {fg = fg_val})
+ eq('Test_hl3 xxx cleared',
+ exec_capture('highlight Test_hl3'))
+ end
+
+ end)
end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 450a76ddac..e49e0b8cc4 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -877,6 +877,24 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq("\nn lhs rhs\n map description",
helpers.exec_capture("nmap lhs"))
end)
+
+ it ('can :filter maps based on description', function()
+ meths.set_keymap('n', 'asdf1', 'qwert', {desc='do the one thing'})
+ meths.set_keymap('n', 'asdf2', 'qwert', {desc='doesnot really do anything'})
+ meths.set_keymap('n', 'asdf3', 'qwert', {desc='do the other thing'})
+ eq([[
+
+n asdf3 qwert
+ do the other thing
+n asdf1 qwert
+ do the one thing]],
+ helpers.exec_capture('filter the nmap'))
+ end)
+
+ it ('shows <nop> as map rhs', function()
+ meths.set_keymap('n', 'asdf', '<nop>', {})
+ eq('\nn asdf <Nop>', helpers.exec_capture('nmap asdf'))
+ end)
end)
describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index 309d9084c8..cdcf08c348 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -181,12 +181,6 @@ describe('server -> client', function()
end)
describe('recursive (child) nvim client', function()
- if helpers.isCI('travis') and helpers.is_os('mac') then
- -- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
- pending("[Hangs on Travis macOS. #5002]", function() end)
- return
- end
-
before_each(function()
command("let vim = rpcstart('"..nvim_prog.."', ['-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--embed', '--headless'])")
neq(0, eval('vim'))
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 937b6559de..71cd055e08 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -23,6 +23,7 @@ local next_msg = helpers.next_msg
local tmpname = helpers.tmpname
local write_file = helpers.write_file
local exec_lua = helpers.exec_lua
+local exc_exec = helpers.exc_exec
local pcall_err = helpers.pcall_err
local format_string = helpers.format_string
@@ -536,6 +537,31 @@ describe('API', function()
end)
end)
+ describe('nvim_set_current_dir', function()
+ local start_dir
+
+ before_each(function()
+ clear()
+ funcs.mkdir("Xtestdir")
+ start_dir = funcs.getcwd()
+ end)
+
+ after_each(function()
+ helpers.rmdir("Xtestdir")
+ end)
+
+ it('works', function()
+ meths.set_current_dir("Xtestdir")
+ eq(funcs.getcwd(), start_dir .. helpers.get_pathsep() .. "Xtestdir")
+ end)
+
+ it('sets previous directory', function()
+ meths.set_current_dir("Xtestdir")
+ meths.exec('cd -', false)
+ eq(funcs.getcwd(), start_dir)
+ end)
+ end)
+
describe('nvim_exec_lua', function()
it('works', function()
meths.exec_lua('vim.api.nvim_set_var("test", 3)', {})
@@ -873,6 +899,19 @@ describe('API', function()
command('lockvar lua')
eq('Key is locked: lua', pcall_err(meths.del_var, 'lua'))
eq('Key is locked: lua', pcall_err(meths.set_var, 'lua', 1))
+
+ -- Check if autoload works properly
+ local pathsep = helpers.get_pathsep()
+ local xconfig = 'Xhome' .. pathsep .. 'Xconfig'
+ local xdata = 'Xhome' .. pathsep .. 'Xdata'
+ local autoload_folder = table.concat({xconfig, 'nvim', 'autoload'}, pathsep)
+ local autoload_file = table.concat({autoload_folder , 'testload.vim'}, pathsep)
+ mkdir_p(autoload_folder)
+ write_file(autoload_file , [[let testload#value = 2]])
+
+ clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_DATA_HOME=xdata } }
+ eq(2, meths.get_var('testload#value'))
+ rmdir('Xhome')
end)
it('nvim_get_vvar, nvim_set_vvar', function()
@@ -2202,6 +2241,14 @@ describe('API', function()
eq({}, meths.get_runtime_file("foobarlang/", true))
end)
+ it('can handle bad patterns', function()
+ if helpers.pending_win32(pending) then return end
+
+ eq("Vim:E220: Missing }.", pcall_err(meths.get_runtime_file, "{", false))
+
+ eq('Vim(echo):E5555: API call: Vim:E220: Missing }.',
+ exc_exec("echo nvim_get_runtime_file('{', v:false)"))
+ end)
end)
describe('nvim_get_all_options_info', function()
diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua
index f4a1642ebf..45dc06b39b 100644
--- a/test/functional/autocmd/dirchanged_spec.lua
+++ b/test/functional/autocmd/dirchanged_spec.lua
@@ -8,7 +8,7 @@ local eval = h.eval
local request = h.request
local iswin = h.iswin
-describe('autocmd DirChanged', function()
+describe('autocmd DirChanged and DirChangedPre', function()
local curdir = string.gsub(lfs.currentdir(), '\\', '/')
local dirs = {
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
@@ -26,31 +26,43 @@ describe('autocmd DirChanged', function()
before_each(function()
clear()
+ command('autocmd DirChangedPre * let [g:evpre, g:amatchpre, g:cdprecount] '
+ ..'= [copy(v:event), expand("<amatch>"), 1 + get(g:, "cdprecount", 0)]')
command('autocmd DirChanged * let [g:getcwd, g:ev, g:amatch, g:cdcount] '
- ..' = [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
+ ..'= [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]')
-- Normalize path separators.
+ command([[autocmd DirChangedPre * let g:evpre['directory'] = substitute(g:evpre['directory'], '\\', '/', 'g')]])
command([[autocmd DirChanged * let g:ev['cwd'] = substitute(g:ev['cwd'], '\\', '/', 'g')]])
- command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
+ command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]])
end)
- it('sets v:event and <amatch>', function()
+ it('set v:event and <amatch>', function()
command('lcd '..dirs[1])
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
command('tcd '..dirs[2])
+ eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
command('cd '..dirs[3])
+ eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
+ eq('global', eval('g:amatchpre'))
eq('global', eval('g:amatch'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
end)
- it('sets getcwd() during event #6260', function()
+ it('DirChanged set getcwd() during event #6260', function()
command('lcd '..dirs[1])
eq(dirs[1], eval('g:getcwd'))
@@ -61,7 +73,7 @@ describe('autocmd DirChanged', function()
eq(dirs[3], eval('g:getcwd'))
end)
- it('disallows recursion', function()
+ it('disallow recursion', function()
command('set shellslash')
-- Set up a _nested_ handler.
command('autocmd DirChanged * nested lcd '..dirs[3])
@@ -72,23 +84,36 @@ describe('autocmd DirChanged', function()
eq(dirs[3], eval('getcwd()'))
end)
- it('does not trigger if :cd fails', function()
+ it('only DirChangedPre is triggered if :cd fails', function()
command('let g:ev = {}')
+ command('let g:cdcount = 0')
local status1, err1 = pcall(function()
- command('lcd '..dirs[1] .. '/doesnotexist')
+ command('lcd '..dirs[1]..'/doesnotexist')
end)
+ eq({directory=dirs[1]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(1, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
local status2, err2 = pcall(function()
- command('lcd '..dirs[2] .. '/doesnotexist')
+ command('lcd '..dirs[2]..'/doesnotexist')
end)
+ eq({directory=dirs[2]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(2, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
local status3, err3 = pcall(function()
- command('lcd '..dirs[3] .. '/doesnotexist')
+ command('lcd '..dirs[3]..'/doesnotexist')
end)
+ eq({directory=dirs[3]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
+ eq(3, eval('g:cdprecount'))
+ eq(0, eval('g:cdcount'))
eq(false, status1)
eq(false, status2)
@@ -99,85 +124,121 @@ describe('autocmd DirChanged', function()
eq('E344:', string.match(err3, "E%d*:"))
end)
- it("is triggered by 'autochdir'", function()
+ it("are triggered by 'autochdir'", function()
command('set autochdir')
command('split '..dirs[1]..'/foo')
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('auto', eval('g:amatchpre'))
eq('auto', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
+ eq(1, eval('g:cdcount'))
command('split '..dirs[2]..'/bar')
+ eq({directory=dirs[2], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev'))
eq('auto', eval('g:amatch'))
-
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
end)
- it('does not trigger if directory has not changed', function()
+ it('do not trigger if directory has not changed', function()
command('lcd '..dirs[1])
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('lcd '..dirs[1])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
if iswin() then
command('lcd '..win_dirs[1])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
eq(1, eval('g:cdcount'))
end
command('tcd '..dirs[2])
+ eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('tcd '..dirs[2])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
if iswin() then
command('tcd '..win_dirs[2])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
eq(2, eval('g:cdcount'))
end
command('cd '..dirs[3])
+ eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev'))
eq('global', eval('g:amatch'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('cd '..dirs[3])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
if iswin() then
command('cd '..win_dirs[3])
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(3, eval('g:cdprecount'))
eq(3, eval('g:cdcount'))
end
command('set autochdir')
command('split '..dirs[1]..'/foo')
+ eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev'))
+ eq('auto', eval('g:amatchpre'))
eq('auto', eval('g:amatch'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
+ command('let g:evpre = {}')
command('let g:ev = {}')
command('split '..dirs[1]..'/bar')
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
if iswin() then
command('split '..win_dirs[1]..'/baz')
+ eq({}, eval('g:evpre'))
eq({}, eval('g:ev'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
end
end)
- it("is triggered by switching to win/tab with different CWD #6054", function()
+ it("are triggered by switching to win/tab with different CWD #6054", function()
command('lcd '..dirs[3]) -- window 3
command('split '..dirs[2]..'/foo') -- window 2
command('lcd '..dirs[2])
@@ -185,72 +246,105 @@ describe('autocmd DirChanged', function()
command('lcd '..dirs[1])
command('2wincmd w') -- window 2
+ eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
+ eq(4, eval('g:cdprecount'))
eq(4, eval('g:cdcount'))
command('tabnew') -- tab 2 (tab-local CWD)
+ eq(4, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(4, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..dirs[3])
command('tabnext') -- tab 1 (no tab-local CWD)
+ eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev'))
+ eq('window', eval('g:amatchpre'))
eq('window', eval('g:amatch'))
command('tabnext') -- tab 2
+ eq({directory=dirs[3], scope='tabpage', changed_window=true}, eval('g:evpre'))
eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev'))
+ eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
+ eq(7, eval('g:cdprecount'))
eq(7, eval('g:cdcount'))
command('tabnext') -- tab 1
command('3wincmd w') -- window 3
+ eq(9, eval('g:cdprecount'))
eq(9, eval('g:cdcount'))
command('tabnext') -- tab 2 (has the *same* CWD)
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
if iswin() then
command('tabnew') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd '..win_dirs[3])
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 2
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('lcd '..win_dirs[3]) -- window 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 2
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
+ eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event
end
end)
- it('is triggered by nvim_set_current_dir()', function()
+ it('are triggered by nvim_set_current_dir()', function()
request('nvim_set_current_dir', dirs[1])
+ eq({directory=dirs[1], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[1], scope='global', changed_window=false}, eval('g:ev'))
+ eq(1, eval('g:cdprecount'))
+ eq(1, eval('g:cdcount'))
request('nvim_set_current_dir', dirs[2])
+ eq({directory=dirs[2], scope='global', changed_window=false}, eval('g:evpre'))
eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev'))
+ eq(2, eval('g:cdprecount'))
+ eq(2, eval('g:cdcount'))
local status, err = pcall(function()
request('nvim_set_current_dir', '/doesnotexist')
end)
eq(false, status)
eq('Failed to change directory', string.match(err, ': (.*)'))
- eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev'))
+ eq({directory='/doesnotexist', scope='global', changed_window=false}, eval('g:evpre'))
+ eq(3, eval('g:cdprecount'))
+ eq(2, eval('g:cdcount'))
end)
- it('works when local to buffer', function()
+ it('work when local to buffer', function()
+ command('let g:triggeredpre = 0')
command('let g:triggered = 0')
+ command('autocmd DirChangedPre <buffer> let g:triggeredpre = 1')
command('autocmd DirChanged <buffer> let g:triggered = 1')
command('cd '..dirs[1])
+ eq(1, eval('g:triggeredpre'))
eq(1, eval('g:triggered'))
end)
end)
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 5e127bce26..0a69660871 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -295,12 +295,6 @@ describe('jobs', function()
end)
it("will not buffer data if it doesn't end in newlines", function()
- if helpers.isCI('travis') and os.getenv('CC') == 'gcc-4.9'
- and helpers.is_os('mac') then
- -- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
- pending("[Hangs on Travis macOS. #5002]")
- end
-
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
nvim('command', 'call jobsend(j, "abc\\nxyz")')
eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg())
diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua
index fdda2be131..c367f8fdd0 100644
--- a/test/functional/editor/put_spec.lua
+++ b/test/functional/editor/put_spec.lua
@@ -507,7 +507,9 @@ describe('put command', function()
return function(exception_table, after_redo)
test_expect(exception_table, after_redo)
if selection_string then
- eq(selection_string, getreg('"'))
+ if not conversion_table.put_backwards then
+ eq(selection_string, getreg('"'))
+ end
else
eq('test_string"', getreg('"'))
end
@@ -714,7 +716,9 @@ describe('put command', function()
expect_base, conversion_table)
return function(exception_table, after_redo)
test_expect(exception_table, after_redo)
- eq('Line of words 1\n', getreg('"'))
+ if not conversion_table.put_backwards then
+ eq('Line of words 1\n', getreg('"'))
+ end
end
end
local base_expect_string = [[
@@ -748,7 +752,9 @@ describe('put command', function()
end, expect_base, conversion_table)
return function(e,c)
test_expect(e,c)
- eq('Lin\nLin', getreg('"'))
+ if not conversion_table.put_backwards then
+ eq('Lin\nLin', getreg('"'))
+ end
end
end
diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua
index f65d9f0d01..f19fab5550 100644
--- a/test/functional/ex_cmds/ctrl_c_spec.lua
+++ b/test/functional/ex_cmds/ctrl_c_spec.lua
@@ -10,8 +10,7 @@ describe("CTRL-C (mapped)", function()
it("interrupts :global", function()
-- Crashes luajit.
- if helpers.skip_fragile(pending,
- helpers.isCI('travis') or helpers.isCI('appveyor')) then
+ if helpers.skip_fragile(pending) then
return
end
diff --git a/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua
new file mode 100644
index 0000000000..d2cf86e4f0
--- /dev/null
+++ b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua
@@ -0,0 +1,7 @@
+local M = {}
+
+M.check = function()
+ return {}
+end
+
+return M
diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua
index c2b22472bf..dd6a9d0bde 100644
--- a/test/functional/legacy/assert_spec.lua
+++ b/test/functional/legacy/assert_spec.lua
@@ -19,36 +19,10 @@ describe('assert function:', function()
clear()
end)
- describe('assert_beeps', function()
- it('works', function()
- call('assert_beeps', 'normal h')
- expected_empty()
- call('assert_beeps', 'normal 0')
- expected_errors({'command did not beep: normal 0'})
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(0, 'normal h'->assert_beeps())
- call assert_equal(1, 'normal 0'->assert_beeps())
- ]]
- expected_errors({tmpname .. ' line 2: command did not beep: normal 0'})
- end)
- end)
-
-- assert_equal({expected}, {actual}, [, {msg}])
describe('assert_equal', function()
it('should not change v:errors when expected is equal to actual', function()
source([[
- let s = 'foo'
- call assert_equal('foo', s)
- let n = 4
- call assert_equal(4, n)
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3], l)
- call assert_equal(v:_null_list, v:_null_list)
- call assert_equal(v:_null_list, [])
- call assert_equal([], v:_null_list)
fu Func()
endfu
let F1 = function('Func')
@@ -98,30 +72,6 @@ describe('assert function:', function()
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call CheckAssert()'))
end)
-
- it('can specify a message and get a message about what failed', function()
- call('assert_equal', 'foo', 'bar', 'testing')
- expected_errors({"testing: Expected 'foo' but got 'bar'"})
- end)
-
- it('should shorten a long message', function()
- call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
- expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"})
- end)
- end)
-
- -- assert_notequal({expected}, {actual}[, {msg}])
- describe('assert_notequal', function()
- it('should not change v:errors when expected differs from actual', function()
- eq(0, call('assert_notequal', 'foo', 4))
- eq(0, call('assert_notequal', {1, 2, 3}, 'foo'))
- expected_empty()
- end)
-
- it('should change v:errors when expected is equal to actual', function()
- eq(1, call('assert_notequal', 'foo', 'foo'))
- expected_errors({"Expected not equal to 'foo'"})
- end)
end)
-- assert_false({actual}, [, {msg}])
@@ -141,14 +91,6 @@ describe('assert function:', function()
call('assert_false', {})
expected_errors({'Expected False but got []'})
end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(0, v:false->assert_false())
- call assert_equal(1, 123->assert_false())
- ]]
- expected_errors({tmpname .. ' line 2: Expected False but got 123'})
- end)
end)
-- assert_true({actual}, [, {msg}])
@@ -164,14 +106,6 @@ describe('assert function:', function()
eq(1, call('assert_true', 1.5))
expected_errors({'Expected True but got 1.5'})
end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(0, v:true->assert_true())
- call assert_equal(1, 0->assert_true())
- ]]
- expected_errors({tmpname .. ' line 2: Expected True but got 0'})
- end)
end)
describe('v:errors', function()
@@ -217,76 +151,10 @@ describe('assert function:', function()
tmpname_two .. " line 1: file two: Expected True but got ''",
})
end)
-
- it('is reset to a list by assert functions', function()
- source([[
- let save_verrors = v:errors
- let v:['errors'] = {'foo': 3}
- call assert_equal('yes', 'no')
- let verrors = v:errors
- let v:errors = save_verrors
- call assert_equal(type([]), type(verrors))
- ]])
- expected_empty()
- end)
- end)
-
- -- assert_match({pat}, {text}[, {msg}])
- describe('assert_match', function()
- it('should not change v:errors when pat matches text', function()
- call('assert_match', '^f.*b.*r$', 'foobar')
- expected_empty()
- end)
-
- it('should change v:errors when pat does not match text', function()
- call('assert_match', 'bar.*foo', 'foobar')
- expected_errors({"Pattern 'bar.*foo' does not match 'foobar'"})
- end)
-
- it('should set v:errors to msg when given and match fails', function()
- call('assert_match', 'bar.*foo', 'foobar', 'wrong')
- expected_errors({"wrong: Pattern 'bar.*foo' does not match 'foobar'"})
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
- ]]
- expected_errors({
- tmpname .. " line 1: wrong: Pattern 'bar.*foo' does not match 'foobar'"
- })
- end)
- end)
-
- -- assert_notmatch({pat}, {text}[, {msg}])
- describe('assert_notmatch', function()
- it('should not change v:errors when pat does not match text', function()
- call('assert_notmatch', 'foo', 'bar')
- call('assert_notmatch', '^foobar$', 'foobars')
- expected_empty()
- end)
-
- it('should change v:errors when pat matches text', function()
- call('assert_notmatch', 'foo', 'foobar')
- expected_errors({"Pattern 'foo' does match 'foobar'"})
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(1, 'foobar'->assert_notmatch('foo'))
- ]]
- expected_errors({tmpname .. " line 1: Pattern 'foo' does match 'foobar'"})
- end)
end)
-- assert_fails({cmd}, [, {error}])
describe('assert_fails', function()
- it('should change v:errors when error does not match v:errmsg', function()
- eq(1, eval([[assert_fails('xxx', 'E12345')]]))
- command([[call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])]])
- expected_errors({"Expected 'E12345' but got 'E492: Not an editor command: xxx': xxx"})
- end)
-
it('should not change v:errors when cmd errors', function()
eq(0, eval([[assert_fails('NonexistentCmd')]]))
expected_empty()
@@ -296,106 +164,5 @@ describe('assert function:', function()
eq(1, eval([[assert_fails('call empty("")', '')]]))
expected_errors({'command did not fail: call empty("")'})
end)
-
- it('can specify and get a message about what failed', function()
- eq(1, eval([[assert_fails('xxx', 'E9876', 'stupid')]]))
- command([[call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])]])
- expected_errors({"stupid: Expected 'E9876' but got 'E492: Not an editor command: xxx': stupid"})
- end)
-
- it('can specify and get a message even when cmd succeeds', function()
- eq(1, eval([[assert_fails('echo', '', 'echo command')]]))
- expected_errors({'command did not fail: echo command'})
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(1, 'echo'->assert_fails('', 'echo command'))
- ]]
- expected_errors({
- tmpname .. ' line 1: command did not fail: echo command'
- })
- end)
- end)
-
- -- assert_inrange({lower}, {upper}, {actual}[, {msg}])
- describe('assert_inrange()', function()
- it('should not change v:errors when actual is in range', function()
- call('assert_inrange', 7, 7, 7)
- call('assert_inrange', 5, 7, 5)
- call('assert_inrange', 5, 7, 6)
- call('assert_inrange', 5, 7, 7)
- expected_empty()
- end)
-
- it('should change v:errors when actual is not in range', function()
- call('assert_inrange', 5, 7, 4)
- call('assert_inrange', 5, 7, 8)
- expected_errors({
- "Expected range 5 - 7, but got 4",
- "Expected range 5 - 7, but got 8",
- })
- end)
-
- it('assert_inrange(1, 1) returns E119', function()
- eq('Vim(call):E119: Not enough arguments for function: assert_inrange',
- exc_exec("call assert_inrange(1, 1)"))
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(0, 5->assert_inrange(5, 7))
- call assert_equal(0, 7->assert_inrange(5, 7))
- call assert_equal(1, 8->assert_inrange(5, 7))
- ]]
- expected_errors({tmpname .. ' line 3: Expected range 5 - 7, but got 8'})
- end)
- end)
-
- -- assert_report({msg})
- describe('assert_report()', function()
- it('should add a message to v:errors', function()
- eq(1, call('assert_report', 'something is wrong'))
- command("call assert_match('something is wrong', v:errors[0])")
- command('call remove(v:errors, 0)')
- expected_empty()
- end)
-
- it('can be used as a method', function()
- local tmpname = source [[
- call assert_equal(1, 'also wrong'->assert_report())
- ]]
- expected_errors({tmpname .. ' line 1: also wrong'})
- end)
- end)
-
- -- assert_exception({cmd}, [, {error}])
- describe('assert_exception()', function()
- it('should assert thrown exceptions properly', function()
- source([[
- try
- nocommand
- catch
- call assert_equal(0, assert_exception('E492'))
- endtry
- ]])
- expected_empty()
- end)
-
- it('should work properly when nested', function()
- source([[
- try
- nocommand
- catch
- try
- " illegal argument, get NULL for error
- call assert_equal(1, assert_exception([]))
- catch
- call assert_equal(0, assert_exception('E730'))
- endtry
- endtry
- ]])
- expected_empty()
- end)
end)
end)
diff --git a/test/functional/legacy/cdo_spec.lua b/test/functional/legacy/cdo_spec.lua
deleted file mode 100644
index 8b3216cbfd..0000000000
--- a/test/functional/legacy/cdo_spec.lua
+++ /dev/null
@@ -1,228 +0,0 @@
--- Tests for the :cdo, :cfdo, :ldo and :lfdo commands
-
-local helpers = require('test.functional.helpers')(after_each)
-local nvim, clear = helpers.meths, helpers.clear
-local call, feed = helpers.call, helpers.feed
-local source, eq = helpers.source, helpers.eq
-
-local function expected_empty()
- eq({}, nvim.get_vvar('errors'))
-end
-
-describe('cdo', function()
- before_each(function()
- clear()
-
- call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile1')
- call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile2')
- call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile3')
-
- source([=[
- " Returns the current line in '<filename> <linenum>L <column>C' format
- function GetRuler()
- return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
- endfunction
-
- " Tests for the :cdo and :ldo commands
- function XdoTests(cchar)
- enew
-
- " Shortcuts for calling the cdo and ldo commands
- let Xdo = a:cchar . 'do'
- let Xgetexpr = a:cchar . 'getexpr'
- let Xprev = a:cchar. 'prev'
- let XdoCmd = Xdo . ' call add(l, GetRuler())'
-
- " Try with an empty list
- let l = []
- exe XdoCmd
- call assert_equal([], l)
-
- " Populate the list and then try
- exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
-
- let l = []
- exe XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- " Run command only on selected error lines
- let l = []
- enew
- exe "2,3" . XdoCmd
- call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- " Boundary condition tests
- let l = []
- enew
- exe "1,1" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C'], l)
-
- let l = []
- enew
- exe "3" . XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
- " Range test commands
- let l = []
- enew
- exe "%" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- let l = []
- enew
- exe "1,$" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- let l = []
- enew
- exe Xprev
- exe "." . XdoCmd
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- let l = []
- enew
- exe "+" . XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
- " Invalid error lines test
- let l = []
- enew
- exe "silent! 27" . XdoCmd
- exe "silent! 4,5" . XdoCmd
- call assert_equal([], l)
-
- " Run commands from an unsaved buffer when 'hidden' is unset
- set nohidden
- let v:errmsg=''
- let l = []
- enew
- setlocal modified
- exe "silent! 2,2" . XdoCmd
- if v:errmsg !~# 'No write since last change'
- call add(v:errors, 'Unsaved file change test failed')
- endif
-
- " If the executed command fails, then the operation should be aborted
- enew!
- let subst_count = 0
- exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
- if subst_count != 1 || getline('.') != 'xLine1'
- call add(v:errors, 'Abort command on error test failed')
- endif
- set hidden
-
- let l = []
- exe "2,2" . Xdo . "! call add(l, GetRuler())"
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- " List with no valid error entries
- let l = []
- edit! +2 Xtestfile1
- exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
- exe XdoCmd
- call assert_equal([], l)
- exe "silent! 2" . XdoCmd
- call assert_equal([], l)
- let v:errmsg=''
- exe "%" . XdoCmd
- exe "1,$" . XdoCmd
- exe "." . XdoCmd
- call assert_equal('', v:errmsg)
-
- " List with only one valid entry
- let l = []
- exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
- exe XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
- endfunction
-
- " Tests for the :cfdo and :lfdo commands
- function XfdoTests(cchar)
- enew
-
- " Shortcuts for calling the cfdo and lfdo commands
- let Xfdo = a:cchar . 'fdo'
- let Xgetexpr = a:cchar . 'getexpr'
- let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
- let Xpfile = a:cchar. 'pfile'
-
- " Clear the quickfix/location list
- exe Xgetexpr . " []"
-
- " Try with an empty list
- let l = []
- exe XfdoCmd
- call assert_equal([], l)
-
- " Populate the list and then try
- exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
-
- let l = []
- exe XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- " Run command only on selected error lines
- let l = []
- exe "2,3" . XfdoCmd
- call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- " Boundary condition tests
- let l = []
- exe "3" . XfdoCmd
- call assert_equal(['Xtestfile3 2L 3C'], l)
-
- " Range test commands
- let l = []
- exe "%" . XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- let l = []
- exe "1,$" . XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- let l = []
- exe Xpfile
- exe "." . XfdoCmd
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- " List with only one valid entry
- let l = []
- exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
- exe XfdoCmd
- call assert_equal(['Xtestfile2 2L 5C'], l)
-
- endfunction
- ]=])
- end)
-
- after_each(function()
- os.remove('Xtestfile1')
- os.remove('Xtestfile2')
- os.remove('Xtestfile3')
- end)
-
- it('works for :cdo', function()
- -- call('XdoTests', 'c')
- feed(":call XdoTests('c')<CR><C-l>")
- expected_empty()
- end)
-
- it('works for :cfdo', function()
- -- call('XfdoTests', 'c')
- feed(":call XfdoTests('c')<CR><C-l>")
- expected_empty()
- end)
-
- it('works for :ldo', function()
- -- call('XdoTests', 'l')
- feed(":call XdoTests('l')<CR><C-l>")
- expected_empty()
- end)
-
- it('works for :lfdo', function()
- -- call('XfdoTests', 'l')
- feed(":call XfdoTests('l')<CR><C-l>")
- expected_empty()
- end)
-end)
diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua
new file mode 100644
index 0000000000..44719027a6
--- /dev/null
+++ b/test/functional/legacy/ex_mode_spec.lua
@@ -0,0 +1,36 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local meths = helpers.meths
+
+before_each(clear)
+
+describe('Ex mode', function()
+ it('supports command line editing', function()
+ local function test_ex_edit(expected, cmd)
+ feed('gQ' .. cmd .. '<C-b>"<CR>')
+ local ret = eval('@:[1:]') -- Remove leading quote.
+ feed('visual<CR>')
+ eq(meths.replace_termcodes(expected, true, true, true), ret)
+ end
+ command('set sw=2')
+ test_ex_edit('bar', 'foo bar<C-u>bar')
+ test_ex_edit('1<C-u>2', '1<C-v><C-u>2')
+ test_ex_edit('213', '1<C-b>2<C-e>3')
+ test_ex_edit('2013', '01<Home>2<End>3')
+ test_ex_edit('0213', '01<Left>2<Right>3')
+ test_ex_edit('0342', '012<Left><Left><Insert>3<Insert>4')
+ test_ex_edit('foo ', 'foo bar<C-w>')
+ test_ex_edit('foo', 'fooba<Del><Del>')
+ test_ex_edit('foobar', 'foo<Tab>bar')
+ test_ex_edit('abbreviate', 'abbrev<Tab>')
+ test_ex_edit('1<C-t><C-t>', '1<C-t><C-t>')
+ test_ex_edit('1<C-t><C-t>', '1<C-t><C-t><C-d>')
+ test_ex_edit(' foo', ' foo<C-d>')
+ test_ex_edit(' foo0', ' foo0<C-d>')
+ test_ex_edit(' foo^', ' foo^<C-d>')
+ end)
+end)
diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua
new file mode 100644
index 0000000000..174f7d292e
--- /dev/null
+++ b/test/functional/legacy/excmd_spec.lua
@@ -0,0 +1,32 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local meths = helpers.meths
+local source = helpers.source
+local eq = helpers.eq
+
+local function sizeoflong()
+ if not exec_lua('return pcall(require, "ffi")') then
+ pending('missing LuaJIT FFI')
+ end
+ return exec_lua('return require("ffi").sizeof(require("ffi").typeof("long"))')
+end
+
+describe('Ex command', function()
+ before_each(clear)
+ after_each(function() eq({}, meths.get_vvar('errors')) end)
+
+ it('checks for address line overflow', function()
+ if sizeoflong() < 8 then
+ pending('Skipped: only works with 64 bit long ints')
+ end
+
+ source [[
+ new
+ call setline(1, 'text')
+ call assert_fails('|.44444444444444444444444', 'E1247:')
+ call assert_fails('|.9223372036854775806', 'E1247:')
+ bwipe!
+ ]]
+ end)
+end)
diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua
new file mode 100644
index 0000000000..6eb853d630
--- /dev/null
+++ b/test/functional/legacy/filechanged_spec.lua
@@ -0,0 +1,131 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear, source = helpers.clear, helpers.source
+local call, eq, meths = helpers.call, helpers.eq, helpers.meths
+
+local function expected_empty()
+ eq({}, meths.get_vvar('errors'))
+end
+
+describe('file changed dialog', function()
+ before_each(function()
+ clear()
+ meths.ui_attach(80, 24, {})
+ meths.set_option('autoread', false)
+ meths.set_option('fsync', true)
+ end)
+
+ it('works', function()
+ if helpers.pending_win32(pending) then return end
+ source([[
+ func Test_file_changed_dialog()
+ au! FileChangedShell
+
+ new Xchanged_d
+ call setline(1, 'reload this')
+ write
+ " Need to wait until the timestamp would change by at least a second.
+ sleep 2
+ silent !echo 'extra line' >>Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W11:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('reload this', getline(1))
+ call assert_equal('extra line', getline(2))
+
+ " delete buffer, only shows an error, no prompt
+ silent !rm Xchanged_d
+ checktime
+ call assert_match('E211:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('extra line', getline(2))
+ let v:warningmsg = 'empty'
+
+ " change buffer, recreate the file and reload
+ call setline(1, 'buffer is changed')
+ silent !echo 'new line' >Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W12:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only mode changed, reload
+ silent !chmod +x Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W16:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only time changed, no prompt
+ sleep 2
+ silent !touch Xchanged_d
+ let v:warningmsg = ''
+ checktime
+ call assert_equal('', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ bwipe!
+ call delete('Xchanged_d')
+ endfunc
+ ]])
+ call('Test_file_changed_dialog')
+ expected_empty()
+ end)
+
+ it('works with FileChangedShell', function()
+ source([[
+ func Test_FileChangedShell_edit_dialog()
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ silent write " Use :silent to prevent a hit-enter prompt
+
+ " File format changed, reload (content only) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call nvim_input('L') " load file content only
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ silent write " Use :silent to prevent a hit-enter prompt
+
+ " File format changed, reload (file and options) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call nvim_input('a') " load file content and options
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal("line1", getline(1))
+ call assert_equal("line2", getline(2))
+ set fileformat=unix
+ silent write " Use :silent to prevent a hit-enter prompt
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+ endfunc
+ ]])
+ call('Test_FileChangedShell_edit_dialog')
+ expected_empty()
+ end)
+end)
diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua
deleted file mode 100644
index 4f9f5a0237..0000000000
--- a/test/functional/legacy/packadd_spec.lua
+++ /dev/null
@@ -1,507 +0,0 @@
--- Tests for 'packpath' and :packadd
-
-local helpers = require('test.functional.helpers')(after_each)
-local clear, source, command = helpers.clear, helpers.source, helpers.command
-local call, eq, nvim = helpers.call, helpers.eq, helpers.meths
-local feed = helpers.feed
-
-local function expected_empty()
- eq({}, nvim.get_vvar('errors'))
-end
-
-describe('packadd', function()
- before_each(function()
- clear()
-
- source([=[
- func Escape(s)
- return escape(a:s, '\~')
- endfunc
-
- func SetUp()
- let s:topdir = expand(getcwd() . '/Xdir')
- if isdirectory(s:topdir)
- call delete(s:topdir, 'rf')
- endif
- exe 'set packpath=' . s:topdir
- let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest')
- endfunc
-
- func TearDown()
- call delete(s:topdir, 'rf')
- endfunc
-
- func Test_packadd()
- if !exists('s:plugdir')
- echomsg 'when running this test manually, call SetUp() first'
- return
- endif
-
- call mkdir(s:plugdir . '/plugin/also', 'p')
- call mkdir(s:plugdir . '/ftdetect', 'p')
- call mkdir(s:plugdir . '/after', 'p')
- set rtp&
- let rtp = &rtp
- filetype on
-
- let rtp_entries = split(rtp, ',')
- for entry in rtp_entries
- if entry =~? '\<after\>'
- let first_after_entry = entry
- break
- endif
- endfor
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 42')
- wq
-
- exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
- call setline(1, 'let g:plugin_also_works = 77')
- wq
-
- exe 'split ' . s:plugdir . '/ftdetect/test.vim'
- call setline(1, 'let g:ftdetect_works = 17')
- wq
-
- packadd mytest
-
- call assert_true(42, g:plugin_works)
- call assert_equal(77, g:plugin_also_works)
- call assert_true(17, g:ftdetect_works)
- call assert_true(len(&rtp) > len(rtp))
- call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp)
-
- let new_after = match(&rtp, Escape(expand(s:plugdir . '/after') . ','))
- let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
- let old_after = match(&rtp, ',' . escape(forwarded, '~') . '\>')
- call assert_true(new_after > 0, 'rtp is ' . &rtp)
- call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
- call assert_true(new_after < old_after, 'rtp is ' . &rtp)
-
- " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
- call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
- let rtp = &rtp
- packadd myte
-
- " Check the path of 'myte' is added
- call assert_true(len(&rtp) > len(rtp))
- call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp)
-
- " Check exception
- call assert_fails("packadd directorynotfound", 'E919:')
- call assert_fails("packadd", 'E471:')
- endfunc
-
- func Test_packadd_start()
- let plugdir = expand(s:topdir . '/pack/mine/start/other')
- call mkdir(plugdir . '/plugin', 'p')
- set rtp&
- let rtp = &rtp
- filetype on
-
- exe 'split ' . plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 24')
- wq
-
- exe 'split ' . plugdir . '/plugin/test.lua'
- call setline(1, 'vim.g.plugin_lua_works = 24')
- wq
-
- packadd other
-
- call assert_equal(24, g:plugin_works)
- call assert_equal(24, g:plugin_lua_works)
- call assert_true(len(&rtp) > len(rtp))
- call assert_match(Escape(plugdir) . '\($\|,\)', &rtp)
- endfunc
-
- func Test_packadd_noload()
- call mkdir(s:plugdir . '/plugin', 'p')
- call mkdir(s:plugdir . '/syntax', 'p')
- set rtp&
- let rtp = &rtp
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 42')
- wq
- exe 'split ' . s:plugdir . '/plugin/test.lua'
- call setline(1, 'let g:plugin_lua_works = 42')
- wq
- let g:plugin_works = 0
- let g:plugin_lua_works = 0
-
- packadd! mytest
-
- call assert_true(len(&rtp) > len(rtp))
- call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp)
- call assert_equal(0, g:plugin_works)
- call assert_equal(0, g:plugin_lua_works)
-
- " check the path is not added twice
- let new_rtp = &rtp
- packadd! mytest
- call assert_equal(new_rtp, &rtp)
- endfunc
-
- func Test_packadd_symlink_dir()
- let top2_dir = expand(s:topdir . '/Xdir2')
- let real_dir = expand(s:topdir . '/Xsym')
- call mkdir(real_dir, 'p')
- if has('win32')
- exec "silent! !mklink /d" top2_dir "Xsym"
- else
- exec "silent! !ln -s Xsym" top2_dir
- endif
- let &rtp = top2_dir . ',' . expand(top2_dir . '/after')
- let &packpath = &rtp
-
- let s:plugdir = expand(top2_dir . '/pack/mine/opt/mytest')
- call mkdir(s:plugdir . '/plugin', 'p')
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 44')
- wq
- let g:plugin_works = 0
-
- packadd mytest
-
- " Must have been inserted in the middle, not at the end
- call assert_match(Escape(expand('/pack/mine/opt/mytest').','), &rtp)
- call assert_equal(44, g:plugin_works)
-
- " No change when doing it again.
- let rtp_before = &rtp
- packadd mytest
- call assert_equal(rtp_before, &rtp)
-
- set rtp&
- let rtp = &rtp
- exec "silent !" (has('win32') ? "rd /q/s" : "rm") top2_dir
- endfunc
-
- func Test_packadd_symlink_dir2()
- let top2_dir = expand(s:topdir . '/Xdir2')
- let real_dir = expand(s:topdir . '/Xsym/pack')
- call mkdir(top2_dir, 'p')
- call mkdir(real_dir, 'p')
- let &rtp = top2_dir . ',' . top2_dir . '/after'
- let &packpath = &rtp
-
- if has('win32')
- exec "silent! !mklink /d" top2_dir "Xsym"
- else
- exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
- endif
- let s:plugdir = expand(top2_dir . '/pack/mine/opt/mytest')
- call mkdir(s:plugdir . '/plugin', 'p')
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 48')
- wq
- let g:plugin_works = 0
-
- packadd mytest
-
- " Must have been inserted in the middle, not at the end
- call assert_match(Escape(expand('/Xdir2/pack/mine/opt/mytest').','), &rtp)
- call assert_equal(48, g:plugin_works)
-
- " No change when doing it again.
- let rtp_before = &rtp
- packadd mytest
- call assert_equal(rtp_before, &rtp)
-
- set rtp&
- let rtp = &rtp
- if has('win32')
- exec "silent !rd /q/s" top2_dir
- else
- exec "silent !rm" top2_dir . '/pack'
- exec "silent !rmdir" top2_dir
- endif
- endfunc
-
- func Test_packloadall()
- " plugin foo with an autoload directory
- let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
- call mkdir(fooplugindir, 'p')
- call writefile(['let g:plugin_foo_number = 1234',
- \ 'let g:plugin_foo_auto = bbb#value',
- \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
- let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
- call mkdir(fooautodir, 'p')
- call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
-
- " plugin aaa with an autoload directory
- let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
- call mkdir(aaaplugindir, 'p')
- call writefile(['let g:plugin_aaa_number = 333',
- \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
- let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
- call mkdir(aaaautodir, 'p')
- call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
-
- " plugin extra with only an autoload directory
- let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
- call mkdir(extraautodir, 'p')
- call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
-
- packloadall
- call assert_equal(1234, g:plugin_foo_number)
- call assert_equal(55, g:plugin_foo_auto)
- call assert_equal(99, g:plugin_extra_auto)
- call assert_equal(333, g:plugin_aaa_number)
- call assert_equal(77, g:plugin_aaa_auto)
-
- " only works once
- call writefile(['let g:plugin_bar_number = 4321'],
- \ fooplugindir . '/bar2.vim')
- packloadall
- call assert_false(exists('g:plugin_bar_number'))
-
- " works when ! used
- packloadall!
- call assert_equal(4321, g:plugin_bar_number)
- endfunc
-
- func Test_helptags()
- let docdir1 = &packpath . '/pack/mine/start/foo/doc'
- let docdir2 = &packpath . '/pack/mine/start/bar/doc'
- call mkdir(docdir1, 'p')
- call mkdir(docdir2, 'p')
- call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
- call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
- exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
-
- helptags ALL
-
- let tags1 = readfile(docdir1 . '/tags')
- call assert_match('look-here', tags1[0])
- let tags2 = readfile(docdir2 . '/tags')
- call assert_match('look-away', tags2[0])
-
- call assert_fails('helptags abcxyz', 'E150:')
- endfunc
-
- func Test_colorscheme()
- let colordirrun = &packpath . '/runtime/colors'
- let colordirstart = &packpath . '/pack/mine/start/foo/colors'
- let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
- call mkdir(colordirrun, 'p')
- call mkdir(colordirstart, 'p')
- call mkdir(colordiropt, 'p')
- call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
- call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
- call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
- exe 'set rtp=' . &packpath . '/runtime'
-
- colorscheme one
- call assert_equal(1, g:found_one)
- colorscheme two
- call assert_equal(1, g:found_two)
- colorscheme three
- call assert_equal(1, g:found_three)
- endfunc
-
- func Test_runtime()
- let rundir = &packpath . '/runtime/extra'
- let startdir = &packpath . '/pack/mine/start/foo/extra'
- let optdir = &packpath . '/pack/mine/opt/bar/extra'
- call mkdir(rundir, 'p')
- call mkdir(startdir, 'p')
- call mkdir(optdir, 'p')
- call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
- call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
- call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
- call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
- call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
- exe 'set rtp=' . &packpath . '/runtime'
-
- let g:sequence = ''
- runtime extra/bar.vim
- call assert_equal('run', g:sequence)
- let g:sequence = ''
- runtime START extra/bar.vim
- call assert_equal('start', g:sequence)
- let g:sequence = ''
- runtime OPT extra/bar.vim
- call assert_equal('opt', g:sequence)
- let g:sequence = ''
- runtime PACK extra/bar.vim
- call assert_equal('start', g:sequence)
- let g:sequence = ''
- runtime! PACK extra/bar.vim
- call assert_equal('startopt', g:sequence)
- let g:sequence = ''
- runtime PACK extra/xxx.vim
- call assert_equal('xxxopt', g:sequence)
-
- let g:sequence = ''
- runtime ALL extra/bar.vim
- call assert_equal('run', g:sequence)
- let g:sequence = ''
- runtime ALL extra/foo.vim
- call assert_equal('foostart', g:sequence)
- let g:sequence = ''
- runtime! ALL extra/xxx.vim
- call assert_equal('xxxopt', g:sequence)
- let g:sequence = ''
- runtime! ALL extra/bar.vim
- call assert_equal('runstartopt', g:sequence)
- endfunc
- ]=])
- call('SetUp')
- end)
-
- after_each(function()
- call('TearDown')
- end)
-
- it('is working', function()
- call('Test_packadd')
- expected_empty()
- end)
-
- it('works with packadd!', function()
- call('Test_packadd_noload')
- expected_empty()
- end)
-
- it('works with symlinks', function()
- call('Test_packadd_symlink_dir')
- expected_empty()
- end)
-
- it('works with :packloadall', function()
- call('Test_packloadall')
- expected_empty()
- end)
-
- it('works with helptags', function()
- call('Test_helptags')
- expected_empty()
- end)
-
- it('works with colorschemes', function()
- call('Test_colorscheme')
- expected_empty()
- end)
-
- it('works with :runtime [what]', function()
- call('Test_runtime')
- expected_empty()
- end)
-
- it('loads packages from "start" directory', function()
- call('Test_packadd_start')
- expected_empty()
- end)
-
- describe('command line completion', function()
- local Screen = require('test.functional.ui.screen')
- local screen
-
- before_each(function()
- screen = Screen.new(30, 5)
- screen:attach()
- screen:set_default_attr_ids({
- [0] = {bold=true, foreground=Screen.colors.Blue},
- [1] = {
- foreground = Screen.colors.Black,
- background = Screen.colors.Yellow,
- },
- [2] = {bold = true, reverse = true}
- })
-
- command([[let optdir1 = &packpath . '/pack/mine/opt']])
- command([[let optdir2 = &packpath . '/pack/candidate/opt']])
- command([[call mkdir(optdir1 . '/pluginA', 'p')]])
- command([[call mkdir(optdir1 . '/pluginC', 'p')]])
- command([[call mkdir(optdir2 . '/pluginB', 'p')]])
- command([[call mkdir(optdir2 . '/pluginC', 'p')]])
- end)
-
- it('works', function()
- feed(':packadd <Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {1:pluginA}{2: pluginB pluginC }|
- :packadd pluginA^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:pluginA }{1:pluginB}{2: pluginC }|
- :packadd pluginB^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:pluginA pluginB }{1:pluginC}{2: }|
- :packadd pluginC^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:pluginA pluginB pluginC }|
- :packadd ^ |
- ]=])
- end)
-
- it('works for colorschemes', function()
- source([[
- let colordirrun = &packpath . '/runtime/colors'
- let colordirstart = &packpath . '/pack/mine/start/foo/colors'
- let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
- call mkdir(colordirrun, 'p')
- call mkdir(colordirstart, 'p')
- call mkdir(colordiropt, 'p')
- call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
- call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
- call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
- exe 'set rtp=' . &packpath . '/runtime']])
-
- feed(':colorscheme <Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {1:one}{2: three two }|
- :colorscheme one^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:one }{1:three}{2: two }|
- :colorscheme three^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:one three }{1:two}{2: }|
- :colorscheme two^ |
- ]=])
- feed('<Tab>')
- screen:expect([=[
- |
- {0:~ }|
- {0:~ }|
- {2:one three two }|
- :colorscheme ^ |
- ]=])
- end)
- end)
-end)
diff --git a/test/functional/legacy/put_spec.lua b/test/functional/legacy/put_spec.lua
new file mode 100644
index 0000000000..3ddf65490e
--- /dev/null
+++ b/test/functional/legacy/put_spec.lua
@@ -0,0 +1,45 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local meths = helpers.meths
+local source = helpers.source
+local eq = helpers.eq
+
+local function sizeoflong()
+ if not exec_lua('return pcall(require, "ffi")') then
+ pending('missing LuaJIT FFI')
+ end
+ return exec_lua('return require("ffi").sizeof(require("ffi").typeof("long"))')
+end
+
+describe('put', function()
+ before_each(clear)
+ after_each(function() eq({}, meths.get_vvar('errors')) end)
+
+ it('very large count 64-bit', function()
+ if sizeoflong() < 8 then
+ pending('Skipped: only works with 64 bit long ints')
+ end
+
+ source [[
+ new
+ let @" = repeat('x', 100)
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+ ]]
+ end)
+
+ it('very large count (visual block) 64-bit', function()
+ if sizeoflong() < 8 then
+ pending('Skipped: only works with 64 bit long ints')
+ end
+
+ source [[
+ new
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>$y"
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+ ]]
+ end)
+end)
diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua
index 8b5fc02d11..67a4bec4c5 100644
--- a/test/functional/legacy/utf8_spec.lua
+++ b/test/functional/legacy/utf8_spec.lua
@@ -28,7 +28,7 @@ describe('utf8', function()
expect([[
start:
axaa
- xあああ
+ xあああ
bxbb]])
end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 17f7a04db6..7ec986acdd 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -19,6 +19,9 @@ local NIL = helpers.NIL
local retry = helpers.retry
local next_msg = helpers.next_msg
local remove_trace = helpers.remove_trace
+local mkdir_p = helpers.mkdir_p
+local rmdir = helpers.rmdir
+local write_file = helpers.write_file
before_each(clear)
@@ -1019,6 +1022,20 @@ describe('lua stdlib', function()
eq(3, exec_lua([[return vim.g.GetCounter()]]))
exec_lua([[vim.api.nvim_get_var('AddCounter')()]])
eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]]))
+
+ -- Check if autoload works properly
+ local pathsep = helpers.get_pathsep()
+ local xconfig = 'Xhome' .. pathsep .. 'Xconfig'
+ local xdata = 'Xhome' .. pathsep .. 'Xdata'
+ local autoload_folder = table.concat({xconfig, 'nvim', 'autoload'}, pathsep)
+ local autoload_file = table.concat({autoload_folder , 'testload.vim'}, pathsep)
+ mkdir_p(autoload_folder)
+ write_file(autoload_file , [[let testload#value = 2]])
+
+ clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_DATA_HOME=xdata } }
+
+ eq(2, exec_lua("return vim.g['testload#value']"))
+ rmdir('Xhome')
end)
it('vim.b', function()
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index f7c2dbdb43..a9bd76ce24 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -153,6 +153,10 @@ describe('health.vim', function()
## report 2
- OK: nothing to see here
+ test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
+ ========================================================================
+ - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty.
+
test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
========================================================================
- ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
@@ -172,6 +176,16 @@ describe('health.vim', function()
]])
end)
+ it("... including empty reports", function()
+ command("checkhealth test_plug.submodule_empty")
+ helpers.expect([[
+
+ test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
+ ========================================================================
+ - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty.
+ ]])
+ end)
+
it("gracefully handles broken lua healthcheck", function()
command("checkhealth test_plug.submodule_failed")
local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 175525b3f2..ed28d8a37d 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -23,7 +23,7 @@ local hl_query = [[
"enum" @type
"extern" @type
- (string_literal) @string
+ (string_literal) @string.nonexistent-specializer-for-string.should-fallback-to-string
(number_literal) @number
(char_literal) @string
@@ -613,4 +613,91 @@ describe('treesitter highlighting', function()
[12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100};
}}
end)
+
+ it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function()
+ if pending_c_parser(pending) then return end
+
+ insert([[
+ char* x = "Will somebody ever read this?";
+ ]])
+
+ screen:expect{grid=[[
+ char* x = "Will somebody ever read this?"; |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua [[
+ local parser = vim.treesitter.get_parser(0, "c", {})
+ local highlighter = vim.treesitter.highlighter
+ highlighter.hl_map['foo.bar'] = 'Type'
+ highlighter.hl_map['foo'] = 'String'
+ test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}})
+ ]]
+
+ screen:expect{grid=[[
+ {3:char}* x = {5:"Will somebody ever read this?"}; |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it("hl_map has the correct fallback behavior", function()
+ exec_lua [[
+ local hl_map = vim.treesitter.highlighter.hl_map
+ hl_map["foo"] = 1
+ hl_map["foo.bar"] = 2
+ hl_map["foo.bar.baz"] = 3
+
+ assert(hl_map["foo"] == 1)
+ assert(hl_map["foo.a.b.c.d"] == 1)
+ assert(hl_map["foo.bar"] == 2)
+ assert(hl_map["foo.bar.a.b.c.d"] == 2)
+ assert(hl_map["foo.bar.baz"] == 3)
+ assert(hl_map["foo.bar.baz.d"] == 3)
+
+ hl_map["FOO"] = 1
+ hl_map["FOO.BAR"] = 2
+ assert(hl_map["FOO.BAR.BAZ"] == 2)
+
+ hl_map["foo.missing.exists"] = 3
+ assert(hl_map["foo.missing"] == 1)
+ assert(hl_map["foo.missing.exists"] == 3)
+ assert(hl_map["foo.missing.exists.bar"] == 3)
+ assert(hl_map["total.nonsense.but.a.lot.of.dots"] == nil)
+ -- It will not perform a second look up of this variable but return a sentinel value
+ assert(hl_map["total.nonsense.but.a.lot.of.dots"] == "__notfound")
+ ]]
+
+ end)
end)
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 396fe5feab..599c74102c 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -285,6 +285,25 @@ end]]
eq(true, result)
end)
+ it('support getting empty text if node range is zero width', function()
+ local text = [[
+```lua
+{}
+```]]
+ insert(text)
+ local result = exec_lua([[
+ local fake_node = {}
+ function fake_node:start()
+ return 1, 0, 7
+ end
+ function fake_node:end_()
+ return 1, 0, 7
+ end
+ return vim.treesitter.get_node_text(fake_node, 0) == ''
+ ]])
+ eq(true, result)
+ end)
+
it('can match special regex characters like \\ * + ( with `vim-match?`', function()
insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 5f29261b17..dc26c52f1a 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -417,6 +417,28 @@ describe('float window', function()
eq(winids, eval('winids'))
end)
+ it('closed when the last non-float window is closed', function()
+ local tabpage = exec_lua([[
+ vim.cmd('edit ./src/nvim/main.c')
+ vim.cmd('tabedit %')
+
+ local buf = vim.api.nvim_create_buf(false, true)
+ local win = vim.api.nvim_open_win(buf, false, {
+ relative = 'win',
+ row = 1,
+ col = 1,
+ width = 10,
+ height = 2
+ })
+
+ vim.cmd('quit')
+
+ return vim.api.nvim_get_current_tabpage()
+ ]])
+
+ eq(1, tabpage)
+ end)
+
local function with_ext_multigrid(multigrid)
local screen
before_each(function()
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 0983d0d4ad..12643eec1e 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local os = require('os')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
-local command = helpers.command
+local command, exec = helpers.command, helpers.exec
local eval, exc_exec = helpers.eval, helpers.exc_exec
local feed_command, eq = helpers.feed_command, helpers.eq
local curbufmeths = helpers.curbufmeths
@@ -1172,6 +1172,105 @@ describe('CursorLine highlight', function()
end)
end)
+describe('ColorColumn highlight', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(40, 15)
+ Screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.LightRed}, -- ColorColumn
+ [2] = {background = Screen.colors.Grey90}, -- CursorLine
+ [3] = {foreground = Screen.colors.Brown}, -- LineNr
+ [4] = {foreground = Screen.colors.Brown, bold = true}, -- CursorLineNr
+ [5] = {foreground = Screen.colors.Blue, bold = true}, -- NonText
+ -- NonText and ColorColumn
+ [6] = {foreground = Screen.colors.Blue, background = Screen.colors.LightRed, bold = true},
+ [7] = {reverse = true, bold = true}, -- StatusLine
+ [8] = {reverse = true}, -- StatusLineNC
+ })
+ screen:attach()
+ end)
+
+ it('when entering a buffer vim-patch:8.1.2073', function()
+ exec([[
+ set nohidden
+ split
+ edit X
+ call setline(1, ["1111111111","22222222222","3333333333"])
+ set nomodified
+ set colorcolumn=3,9
+ set number cursorline cursorlineopt=number
+ wincmd w
+ buf X
+ ]])
+ screen:expect([[
+ {4: 1 }11{1:1}11111{1:1}1 |
+ {3: 2 }22{1:2}22222{1:2}22 |
+ {3: 3 }33{1:3}33333{1:3}3 |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {8:X }|
+ {4: 1 }^11{1:1}11111{1:1}1 |
+ {3: 2 }22{1:2}22222{1:2}22 |
+ {3: 3 }33{1:3}33333{1:3}3 |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {7:X }|
+ |
+ ]])
+ end)
+
+ it("in 'breakindent' vim-patch:8.2.1689", function()
+ exec([[
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ set co=40 linebreak bri briopt=shift:2 cc=40,41,43
+ ]])
+ screen:expect([[
+ ^The quick brown fox jumped over the {1: }|
+ {1: } {1:l}azy dogs |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ |
+ ]])
+ end)
+
+ it("in 'showbreak' vim-patch:8.2.1689", function()
+ exec([[
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ set co=40 showbreak=+++>\\ cc=40,41,43
+ ]])
+ screen:expect([[
+ ^The quick brown fox jumped over the laz{1:y}|
+ {6:+}{5:+}{6:+}{5:>\} dogs |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ |
+ ]])
+ end)
+end)
describe("MsgSeparator highlight and msgsep fillchar", function()
local screen
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index dcd31cfdb7..a0b7c98c51 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -526,6 +526,32 @@ describe('Signs', function()
]])
end)
+ it('shows the line number when signcolumn=number but no marks on a line have text', function()
+ feed('ia<cr>b<cr>c<cr><esc>')
+ command('set number signcolumn=number')
+ command('sign define pietSearch text=>> texthl=Search numhl=Error')
+ command('sign define pietError text= texthl=Search numhl=Error')
+ command('sign place 1 line=1 name=pietSearch buffer=1')
+ command('sign place 2 line=2 name=pietError buffer=1')
+ -- no signcolumn, line number for "a" is Search, for "b" is Error, for "c" is LineNr
+ screen:expect([[
+ {1: >> }a |
+ {8: 2 }b |
+ {6: 3 }c |
+ {6: 4 }^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
+
it('can have 32bit sign IDs', function()
command('sign define piet text=>> texthl=Search')
command('sign place 100000 line=1 name=piet buffer=1')
diff --git a/test/helpers.lua b/test/helpers.lua
index 522714c8be..333e98d495 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -801,12 +801,10 @@ end
function module.isCI(name)
local any = (name == nil)
- assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut' or name == 'github')
- local av = ((any or name == 'appveyor') and nil ~= os.getenv('APPVEYOR'))
- local tr = ((any or name == 'travis') and nil ~= os.getenv('TRAVIS'))
+ assert(any or name == 'sourcehut' or name == 'github')
local sh = ((any or name == 'sourcehut') and nil ~= os.getenv('SOURCEHUT'))
local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS'))
- return tr or av or sh or gh
+ return sh or gh
end
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index cdca7c7b9c..174f1cbaba 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -163,10 +163,10 @@ set(LUAROCKS_SHA256 ab6612ca9ab87c6984871d2712d05525775e8b50172701a0a1cabddf76de
set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/92d929f.tar.gz)
set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a02e4b)
-set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
+set(LIBTERMKEY_URL https://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600)
-set(LIBVTERM_URL http://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz)
+set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz)
set(LIBVTERM_SHA256 bc70349e95559c667672fc8c55b9527d9db9ada0fb80a3beda533418d782d3dd)
set(LUV_VERSION 1.42.0-1)